From cee712105850ac3385cd0091a923438167433f9f Mon Sep 17 00:00:00 2001 From: TSR Berry <20988865+TSRBerry@users.noreply.github.com> Date: Sat, 8 Apr 2023 01:22:00 +0200 Subject: Move solution and projects to src --- src/ARMeilleure/ARMeilleure.csproj | 26 + src/ARMeilleure/Allocators.cs | 42 + src/ARMeilleure/CodeGen/Arm64/Arm64Optimizer.cs | 270 + src/ARMeilleure/CodeGen/Arm64/ArmCondition.cs | 47 + src/ARMeilleure/CodeGen/Arm64/ArmExtensionType.cs | 14 + src/ARMeilleure/CodeGen/Arm64/ArmShiftType.cs | 11 + src/ARMeilleure/CodeGen/Arm64/Assembler.cs | 1160 +++++ src/ARMeilleure/CodeGen/Arm64/CallingConvention.cs | 96 + src/ARMeilleure/CodeGen/Arm64/CodeGenCommon.cs | 91 + src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs | 287 ++ src/ARMeilleure/CodeGen/Arm64/CodeGenerator.cs | 1580 ++++++ .../CodeGen/Arm64/CodeGeneratorIntrinsic.cs | 662 +++ .../CodeGen/Arm64/HardwareCapabilities.cs | 185 + src/ARMeilleure/CodeGen/Arm64/IntrinsicInfo.cs | 14 + src/ARMeilleure/CodeGen/Arm64/IntrinsicTable.cs | 463 ++ src/ARMeilleure/CodeGen/Arm64/IntrinsicType.cs | 59 + src/ARMeilleure/CodeGen/Arm64/PreAllocator.cs | 892 ++++ src/ARMeilleure/CodeGen/CompiledFunction.cs | 68 + src/ARMeilleure/CodeGen/Linking/RelocEntry.cs | 38 + src/ARMeilleure/CodeGen/Linking/RelocInfo.cs | 32 + src/ARMeilleure/CodeGen/Linking/Symbol.cs | 99 + src/ARMeilleure/CodeGen/Linking/SymbolType.cs | 28 + .../CodeGen/Optimizations/BlockPlacement.cs | 72 + .../CodeGen/Optimizations/ConstantFolding.cs | 346 ++ src/ARMeilleure/CodeGen/Optimizations/Optimizer.cs | 252 + .../CodeGen/Optimizations/Simplification.cs | 183 + src/ARMeilleure/CodeGen/Optimizations/TailMerge.cs | 83 + src/ARMeilleure/CodeGen/PreAllocatorCommon.cs | 57 + .../CodeGen/RegisterAllocators/AllocationResult.cs | 19 + .../CodeGen/RegisterAllocators/CopyResolver.cs | 259 + .../CodeGen/RegisterAllocators/HybridAllocator.cs | 454 ++ .../RegisterAllocators/IRegisterAllocator.cs | 12 + .../RegisterAllocators/LinearScanAllocator.cs | 1101 ++++ .../CodeGen/RegisterAllocators/LiveInterval.cs | 396 ++ .../CodeGen/RegisterAllocators/LiveIntervalList.cs | 40 + .../CodeGen/RegisterAllocators/LiveRange.cs | 74 + .../CodeGen/RegisterAllocators/RegisterMasks.cs | 50 + .../CodeGen/RegisterAllocators/StackAllocator.cs | 25 + .../CodeGen/RegisterAllocators/UseList.cs | 84 + src/ARMeilleure/CodeGen/Unwinding/UnwindInfo.cs | 16 + .../CodeGen/Unwinding/UnwindPseudoOp.cs | 11 + .../CodeGen/Unwinding/UnwindPushEntry.cs | 20 + src/ARMeilleure/CodeGen/X86/Assembler.cs | 1559 ++++++ src/ARMeilleure/CodeGen/X86/AssemblerTable.cs | 295 ++ src/ARMeilleure/CodeGen/X86/CallConvName.cs | 8 + src/ARMeilleure/CodeGen/X86/CallingConvention.cs | 158 + src/ARMeilleure/CodeGen/X86/CodeGenCommon.cs | 19 + src/ARMeilleure/CodeGen/X86/CodeGenContext.cs | 105 + src/ARMeilleure/CodeGen/X86/CodeGenerator.cs | 1865 +++++++ .../CodeGen/X86/HardwareCapabilities.cs | 144 + src/ARMeilleure/CodeGen/X86/IntrinsicInfo.cs | 14 + src/ARMeilleure/CodeGen/X86/IntrinsicTable.cs | 200 + src/ARMeilleure/CodeGen/X86/IntrinsicType.cs | 18 + src/ARMeilleure/CodeGen/X86/Mxcsr.cs | 15 + src/ARMeilleure/CodeGen/X86/PreAllocator.cs | 796 +++ src/ARMeilleure/CodeGen/X86/PreAllocatorSystemV.cs | 334 ++ src/ARMeilleure/CodeGen/X86/PreAllocatorWindows.cs | 327 ++ src/ARMeilleure/CodeGen/X86/X86Condition.cs | 47 + src/ARMeilleure/CodeGen/X86/X86Instruction.cs | 231 + src/ARMeilleure/CodeGen/X86/X86Optimizer.cs | 259 + src/ARMeilleure/CodeGen/X86/X86Register.cs | 41 + src/ARMeilleure/Common/AddressTable.cs | 252 + src/ARMeilleure/Common/Allocator.cs | 24 + src/ARMeilleure/Common/ArenaAllocator.cs | 187 + src/ARMeilleure/Common/BitMap.cs | 222 + src/ARMeilleure/Common/BitUtils.cs | 57 + src/ARMeilleure/Common/Counter.cs | 98 + src/ARMeilleure/Common/EntryTable.cs | 188 + src/ARMeilleure/Common/EnumUtils.cs | 12 + src/ARMeilleure/Common/NativeAllocator.cs | 27 + src/ARMeilleure/Decoders/Block.cs | 101 + src/ARMeilleure/Decoders/Condition.cs | 32 + src/ARMeilleure/Decoders/DataOp.cs | 10 + src/ARMeilleure/Decoders/Decoder.cs | 391 ++ src/ARMeilleure/Decoders/DecoderHelper.cs | 167 + src/ARMeilleure/Decoders/DecoderMode.cs | 9 + src/ARMeilleure/Decoders/IOpCode.cs | 17 + src/ARMeilleure/Decoders/IOpCode32.cs | 9 + src/ARMeilleure/Decoders/IOpCode32Adr.cs | 9 + src/ARMeilleure/Decoders/IOpCode32Alu.cs | 8 + src/ARMeilleure/Decoders/IOpCode32AluBf.cs | 14 + src/ARMeilleure/Decoders/IOpCode32AluImm.cs | 9 + src/ARMeilleure/Decoders/IOpCode32AluImm16.cs | 7 + src/ARMeilleure/Decoders/IOpCode32AluMla.cs | 11 + src/ARMeilleure/Decoders/IOpCode32AluReg.cs | 7 + src/ARMeilleure/Decoders/IOpCode32AluRsImm.cs | 10 + src/ARMeilleure/Decoders/IOpCode32AluRsReg.cs | 10 + src/ARMeilleure/Decoders/IOpCode32AluUmull.cs | 13 + src/ARMeilleure/Decoders/IOpCode32AluUx.cs | 8 + src/ARMeilleure/Decoders/IOpCode32BImm.cs | 4 + src/ARMeilleure/Decoders/IOpCode32BReg.cs | 7 + src/ARMeilleure/Decoders/IOpCode32Exception.cs | 7 + src/ARMeilleure/Decoders/IOpCode32HasSetFlags.cs | 7 + src/ARMeilleure/Decoders/IOpCode32Mem.cs | 16 + src/ARMeilleure/Decoders/IOpCode32MemEx.cs | 7 + src/ARMeilleure/Decoders/IOpCode32MemMult.cs | 15 + src/ARMeilleure/Decoders/IOpCode32MemReg.cs | 7 + src/ARMeilleure/Decoders/IOpCode32MemRsImm.cs | 8 + src/ARMeilleure/Decoders/IOpCode32Simd.cs | 4 + src/ARMeilleure/Decoders/IOpCode32SimdImm.cs | 9 + src/ARMeilleure/Decoders/IOpCodeAlu.cs | 10 + src/ARMeilleure/Decoders/IOpCodeAluImm.cs | 7 + src/ARMeilleure/Decoders/IOpCodeAluRs.cs | 10 + src/ARMeilleure/Decoders/IOpCodeAluRx.cs | 10 + src/ARMeilleure/Decoders/IOpCodeBImm.cs | 7 + src/ARMeilleure/Decoders/IOpCodeCond.cs | 7 + src/ARMeilleure/Decoders/IOpCodeLit.cs | 11 + src/ARMeilleure/Decoders/IOpCodeSimd.cs | 7 + src/ARMeilleure/Decoders/InstDescriptor.cs | 18 + src/ARMeilleure/Decoders/InstEmitter.cs | 6 + src/ARMeilleure/Decoders/IntType.cs | 14 + src/ARMeilleure/Decoders/OpCode.cs | 49 + src/ARMeilleure/Decoders/OpCode32.cs | 34 + src/ARMeilleure/Decoders/OpCode32Alu.cs | 20 + src/ARMeilleure/Decoders/OpCode32AluBf.cs | 22 + src/ARMeilleure/Decoders/OpCode32AluImm.cs | 23 + src/ARMeilleure/Decoders/OpCode32AluImm16.cs | 17 + src/ARMeilleure/Decoders/OpCode32AluMla.cs | 30 + src/ARMeilleure/Decoders/OpCode32AluReg.cs | 14 + src/ARMeilleure/Decoders/OpCode32AluRsImm.cs | 20 + src/ARMeilleure/Decoders/OpCode32AluRsReg.cs | 20 + src/ARMeilleure/Decoders/OpCode32AluUmull.cs | 30 + src/ARMeilleure/Decoders/OpCode32AluUx.cs | 18 + src/ARMeilleure/Decoders/OpCode32BImm.cs | 29 + src/ARMeilleure/Decoders/OpCode32BReg.cs | 14 + src/ARMeilleure/Decoders/OpCode32Exception.cs | 14 + src/ARMeilleure/Decoders/OpCode32Mem.cs | 39 + src/ARMeilleure/Decoders/OpCode32MemImm.cs | 12 + src/ARMeilleure/Decoders/OpCode32MemImm8.cs | 15 + src/ARMeilleure/Decoders/OpCode32MemLdEx.cs | 14 + src/ARMeilleure/Decoders/OpCode32MemMult.cs | 52 + src/ARMeilleure/Decoders/OpCode32MemReg.cs | 14 + src/ARMeilleure/Decoders/OpCode32MemRsImm.cs | 18 + src/ARMeilleure/Decoders/OpCode32MemStEx.cs | 15 + src/ARMeilleure/Decoders/OpCode32Mrs.cs | 16 + src/ARMeilleure/Decoders/OpCode32MsrReg.cs | 29 + src/ARMeilleure/Decoders/OpCode32Sat.cs | 24 + src/ARMeilleure/Decoders/OpCode32Sat16.cs | 18 + src/ARMeilleure/Decoders/OpCode32Simd.cs | 33 + src/ARMeilleure/Decoders/OpCode32SimdBase.cs | 55 + src/ARMeilleure/Decoders/OpCode32SimdBinary.cs | 21 + src/ARMeilleure/Decoders/OpCode32SimdCmpZ.cs | 18 + src/ARMeilleure/Decoders/OpCode32SimdCvtFI.cs | 24 + src/ARMeilleure/Decoders/OpCode32SimdCvtTB.cs | 44 + src/ARMeilleure/Decoders/OpCode32SimdDupElem.cs | 43 + src/ARMeilleure/Decoders/OpCode32SimdDupGP.cs | 36 + src/ARMeilleure/Decoders/OpCode32SimdExt.cs | 20 + src/ARMeilleure/Decoders/OpCode32SimdImm.cs | 38 + src/ARMeilleure/Decoders/OpCode32SimdImm44.cs | 41 + src/ARMeilleure/Decoders/OpCode32SimdLong.cs | 30 + src/ARMeilleure/Decoders/OpCode32SimdMemImm.cs | 40 + src/ARMeilleure/Decoders/OpCode32SimdMemMult.cs | 76 + src/ARMeilleure/Decoders/OpCode32SimdMemPair.cs | 50 + src/ARMeilleure/Decoders/OpCode32SimdMemSingle.cs | 51 + src/ARMeilleure/Decoders/OpCode32SimdMovGp.cs | 31 + .../Decoders/OpCode32SimdMovGpDouble.cs | 36 + src/ARMeilleure/Decoders/OpCode32SimdMovGpElem.cs | 51 + src/ARMeilleure/Decoders/OpCode32SimdMovn.cs | 13 + src/ARMeilleure/Decoders/OpCode32SimdReg.cs | 25 + src/ARMeilleure/Decoders/OpCode32SimdRegElem.cs | 31 + .../Decoders/OpCode32SimdRegElemLong.cs | 22 + src/ARMeilleure/Decoders/OpCode32SimdRegLong.cs | 24 + src/ARMeilleure/Decoders/OpCode32SimdRegS.cs | 23 + src/ARMeilleure/Decoders/OpCode32SimdRegWide.cs | 20 + src/ARMeilleure/Decoders/OpCode32SimdRev.cs | 23 + src/ARMeilleure/Decoders/OpCode32SimdS.cs | 39 + src/ARMeilleure/Decoders/OpCode32SimdSel.cs | 23 + src/ARMeilleure/Decoders/OpCode32SimdShImm.cs | 46 + src/ARMeilleure/Decoders/OpCode32SimdShImmLong.cs | 43 + .../Decoders/OpCode32SimdShImmNarrow.cs | 10 + src/ARMeilleure/Decoders/OpCode32SimdSpecial.cs | 19 + src/ARMeilleure/Decoders/OpCode32SimdSqrte.cs | 19 + src/ARMeilleure/Decoders/OpCode32SimdTbl.cs | 24 + src/ARMeilleure/Decoders/OpCode32System.cs | 28 + src/ARMeilleure/Decoders/OpCodeAdr.cs | 19 + src/ARMeilleure/Decoders/OpCodeAlu.cs | 23 + src/ARMeilleure/Decoders/OpCodeAluBinary.cs | 14 + src/ARMeilleure/Decoders/OpCodeAluImm.cs | 40 + src/ARMeilleure/Decoders/OpCodeAluRs.cs | 29 + src/ARMeilleure/Decoders/OpCodeAluRx.cs | 19 + src/ARMeilleure/Decoders/OpCodeBImm.cs | 11 + src/ARMeilleure/Decoders/OpCodeBImmAl.cs | 12 + src/ARMeilleure/Decoders/OpCodeBImmCmp.cs | 20 + src/ARMeilleure/Decoders/OpCodeBImmCond.cs | 25 + src/ARMeilleure/Decoders/OpCodeBImmTest.cs | 20 + src/ARMeilleure/Decoders/OpCodeBReg.cs | 24 + src/ARMeilleure/Decoders/OpCodeBfm.cs | 29 + src/ARMeilleure/Decoders/OpCodeCcmp.cs | 32 + src/ARMeilleure/Decoders/OpCodeCcmpImm.cs | 11 + src/ARMeilleure/Decoders/OpCodeCcmpReg.cs | 15 + src/ARMeilleure/Decoders/OpCodeCsel.cs | 17 + src/ARMeilleure/Decoders/OpCodeException.cs | 14 + src/ARMeilleure/Decoders/OpCodeMem.cs | 19 + src/ARMeilleure/Decoders/OpCodeMemEx.cs | 16 + src/ARMeilleure/Decoders/OpCodeMemImm.cs | 53 + src/ARMeilleure/Decoders/OpCodeMemLit.cs | 28 + src/ARMeilleure/Decoders/OpCodeMemPair.cs | 25 + src/ARMeilleure/Decoders/OpCodeMemReg.cs | 20 + src/ARMeilleure/Decoders/OpCodeMov.cs | 38 + src/ARMeilleure/Decoders/OpCodeMul.cs | 16 + src/ARMeilleure/Decoders/OpCodeSimd.cs | 24 + src/ARMeilleure/Decoders/OpCodeSimdCvt.cs | 21 + src/ARMeilleure/Decoders/OpCodeSimdExt.cs | 14 + src/ARMeilleure/Decoders/OpCodeSimdFcond.cs | 17 + src/ARMeilleure/Decoders/OpCodeSimdFmov.cs | 32 + src/ARMeilleure/Decoders/OpCodeSimdHelper.cs | 88 + src/ARMeilleure/Decoders/OpCodeSimdImm.cs | 107 + src/ARMeilleure/Decoders/OpCodeSimdIns.cs | 36 + src/ARMeilleure/Decoders/OpCodeSimdMemImm.cs | 28 + src/ARMeilleure/Decoders/OpCodeSimdMemLit.cs | 31 + src/ARMeilleure/Decoders/OpCodeSimdMemMs.cs | 48 + src/ARMeilleure/Decoders/OpCodeSimdMemPair.cs | 16 + src/ARMeilleure/Decoders/OpCodeSimdMemReg.cs | 21 + src/ARMeilleure/Decoders/OpCodeSimdMemSs.cs | 97 + src/ARMeilleure/Decoders/OpCodeSimdReg.cs | 18 + src/ARMeilleure/Decoders/OpCodeSimdRegElem.cs | 31 + src/ARMeilleure/Decoders/OpCodeSimdRegElemF.cs | 33 + src/ARMeilleure/Decoders/OpCodeSimdShImm.cs | 18 + src/ARMeilleure/Decoders/OpCodeSimdTbl.cs | 12 + src/ARMeilleure/Decoders/OpCodeSystem.cs | 24 + src/ARMeilleure/Decoders/OpCodeT16.cs | 15 + src/ARMeilleure/Decoders/OpCodeT16AddSubImm3.cs | 24 + src/ARMeilleure/Decoders/OpCodeT16AddSubReg.cs | 20 + src/ARMeilleure/Decoders/OpCodeT16AddSubSp.cs | 23 + src/ARMeilleure/Decoders/OpCodeT16Adr.cs | 19 + src/ARMeilleure/Decoders/OpCodeT16AluImm8.cs | 24 + src/ARMeilleure/Decoders/OpCodeT16AluImmZero.cs | 24 + src/ARMeilleure/Decoders/OpCodeT16AluRegHigh.cs | 20 + src/ARMeilleure/Decoders/OpCodeT16AluRegLow.cs | 20 + src/ARMeilleure/Decoders/OpCodeT16AluUx.cs | 22 + src/ARMeilleure/Decoders/OpCodeT16BImm11.cs | 15 + src/ARMeilleure/Decoders/OpCodeT16BImm8.cs | 17 + src/ARMeilleure/Decoders/OpCodeT16BImmCmp.cs | 19 + src/ARMeilleure/Decoders/OpCodeT16BReg.cs | 14 + src/ARMeilleure/Decoders/OpCodeT16Exception.cs | 14 + src/ARMeilleure/Decoders/OpCodeT16IfThen.cs | 33 + src/ARMeilleure/Decoders/OpCodeT16MemImm5.cs | 58 + src/ARMeilleure/Decoders/OpCodeT16MemLit.cs | 26 + src/ARMeilleure/Decoders/OpCodeT16MemMult.cs | 34 + src/ARMeilleure/Decoders/OpCodeT16MemReg.cs | 27 + src/ARMeilleure/Decoders/OpCodeT16MemSp.cs | 28 + src/ARMeilleure/Decoders/OpCodeT16MemStack.cs | 42 + src/ARMeilleure/Decoders/OpCodeT16ShiftImm.cs | 24 + src/ARMeilleure/Decoders/OpCodeT16ShiftReg.cs | 27 + src/ARMeilleure/Decoders/OpCodeT16SpRel.cs | 24 + src/ARMeilleure/Decoders/OpCodeT32.cs | 15 + src/ARMeilleure/Decoders/OpCodeT32Alu.cs | 20 + src/ARMeilleure/Decoders/OpCodeT32AluBf.cs | 22 + src/ARMeilleure/Decoders/OpCodeT32AluImm.cs | 38 + src/ARMeilleure/Decoders/OpCodeT32AluImm12.cs | 16 + src/ARMeilleure/Decoders/OpCodeT32AluMla.cs | 29 + src/ARMeilleure/Decoders/OpCodeT32AluReg.cs | 14 + src/ARMeilleure/Decoders/OpCodeT32AluRsImm.cs | 20 + src/ARMeilleure/Decoders/OpCodeT32AluUmull.cs | 28 + src/ARMeilleure/Decoders/OpCodeT32AluUx.cs | 18 + src/ARMeilleure/Decoders/OpCodeT32BImm20.cs | 27 + src/ARMeilleure/Decoders/OpCodeT32BImm24.cs | 35 + src/ARMeilleure/Decoders/OpCodeT32MemImm12.cs | 25 + src/ARMeilleure/Decoders/OpCodeT32MemImm8.cs | 29 + src/ARMeilleure/Decoders/OpCodeT32MemImm8D.cs | 31 + src/ARMeilleure/Decoders/OpCodeT32MemLdEx.cs | 26 + src/ARMeilleure/Decoders/OpCodeT32MemMult.cs | 52 + src/ARMeilleure/Decoders/OpCodeT32MemRsImm.cs | 30 + src/ARMeilleure/Decoders/OpCodeT32MemStEx.cs | 27 + src/ARMeilleure/Decoders/OpCodeT32MovImm16.cs | 16 + src/ARMeilleure/Decoders/OpCodeT32ShiftReg.cs | 19 + src/ARMeilleure/Decoders/OpCodeT32Tb.cs | 16 + src/ARMeilleure/Decoders/OpCodeTable.cs | 1509 ++++++ .../Decoders/Optimizations/TailCallRemover.cs | 88 + src/ARMeilleure/Decoders/RegisterSize.cs | 10 + src/ARMeilleure/Decoders/ShiftType.cs | 10 + src/ARMeilleure/Diagnostics/IRDumper.cs | 311 ++ src/ARMeilleure/Diagnostics/Logger.cs | 56 + src/ARMeilleure/Diagnostics/PassName.cs | 19 + src/ARMeilleure/Diagnostics/Symbols.cs | 84 + .../Diagnostics/TranslatorEventSource.cs | 67 + src/ARMeilleure/Instructions/CryptoHelper.cs | 280 + src/ARMeilleure/Instructions/InstEmitAlu.cs | 400 ++ src/ARMeilleure/Instructions/InstEmitAlu32.cs | 931 ++++ src/ARMeilleure/Instructions/InstEmitAluHelper.cs | 613 +++ src/ARMeilleure/Instructions/InstEmitBfm.cs | 196 + src/ARMeilleure/Instructions/InstEmitCcmp.cs | 61 + src/ARMeilleure/Instructions/InstEmitCsel.cs | 53 + src/ARMeilleure/Instructions/InstEmitDiv.cs | 67 + src/ARMeilleure/Instructions/InstEmitException.cs | 55 + .../Instructions/InstEmitException32.cs | 39 + src/ARMeilleure/Instructions/InstEmitFlow.cs | 107 + src/ARMeilleure/Instructions/InstEmitFlow32.cs | 136 + src/ARMeilleure/Instructions/InstEmitFlowHelper.cs | 240 + src/ARMeilleure/Instructions/InstEmitHash.cs | 69 + src/ARMeilleure/Instructions/InstEmitHash32.cs | 53 + src/ARMeilleure/Instructions/InstEmitHashHelper.cs | 118 + src/ARMeilleure/Instructions/InstEmitHelper.cs | 264 + src/ARMeilleure/Instructions/InstEmitMemory.cs | 184 + src/ARMeilleure/Instructions/InstEmitMemory32.cs | 265 + src/ARMeilleure/Instructions/InstEmitMemoryEx.cs | 178 + src/ARMeilleure/Instructions/InstEmitMemoryEx32.cs | 237 + .../Instructions/InstEmitMemoryExHelper.cs | 174 + .../Instructions/InstEmitMemoryHelper.cs | 648 +++ src/ARMeilleure/Instructions/InstEmitMove.cs | 41 + src/ARMeilleure/Instructions/InstEmitMul.cs | 100 + src/ARMeilleure/Instructions/InstEmitMul32.cs | 379 ++ .../Instructions/InstEmitSimdArithmetic.cs | 5224 +++++++++++++++++++ .../Instructions/InstEmitSimdArithmetic32.cs | 1703 +++++++ src/ARMeilleure/Instructions/InstEmitSimdCmp.cs | 799 +++ src/ARMeilleure/Instructions/InstEmitSimdCmp32.cs | 437 ++ src/ARMeilleure/Instructions/InstEmitSimdCrypto.cs | 99 + .../Instructions/InstEmitSimdCrypto32.cs | 99 + src/ARMeilleure/Instructions/InstEmitSimdCvt.cs | 1891 +++++++ src/ARMeilleure/Instructions/InstEmitSimdCvt32.cs | 800 +++ src/ARMeilleure/Instructions/InstEmitSimdHash.cs | 147 + src/ARMeilleure/Instructions/InstEmitSimdHash32.cs | 64 + .../Instructions/InstEmitSimdHashHelper.cs | 56 + src/ARMeilleure/Instructions/InstEmitSimdHelper.cs | 2088 ++++++++ .../Instructions/InstEmitSimdHelper32.cs | 1286 +++++ .../Instructions/InstEmitSimdHelper32Arm64.cs | 366 ++ .../Instructions/InstEmitSimdHelperArm64.cs | 720 +++ .../Instructions/InstEmitSimdLogical.cs | 612 +++ .../Instructions/InstEmitSimdLogical32.cs | 266 + src/ARMeilleure/Instructions/InstEmitSimdMemory.cs | 160 + .../Instructions/InstEmitSimdMemory32.cs | 352 ++ src/ARMeilleure/Instructions/InstEmitSimdMove.cs | 850 ++++ src/ARMeilleure/Instructions/InstEmitSimdMove32.cs | 656 +++ src/ARMeilleure/Instructions/InstEmitSimdShift.cs | 1827 +++++++ .../Instructions/InstEmitSimdShift32.cs | 389 ++ src/ARMeilleure/Instructions/InstEmitSystem.cs | 248 + src/ARMeilleure/Instructions/InstEmitSystem32.cs | 351 ++ src/ARMeilleure/Instructions/InstName.cs | 685 +++ src/ARMeilleure/Instructions/NativeInterface.cs | 195 + src/ARMeilleure/Instructions/SoftFallback.cs | 624 +++ src/ARMeilleure/Instructions/SoftFloat.cs | 3480 +++++++++++++ .../IntermediateRepresentation/BasicBlock.cs | 159 + .../BasicBlockFrequency.cs | 8 + .../IntermediateRepresentation/Comparison.cs | 24 + .../IIntrusiveListNode.cs | 8 + .../IntermediateRepresentation/Instruction.cs | 72 + .../IntermediateRepresentation/Intrinsic.cs | 636 +++ .../IntermediateRepresentation/IntrusiveList.cs | 208 + .../IntermediateRepresentation/MemoryOperand.cs | 54 + .../IntermediateRepresentation/Multiplier.cs | 11 + .../IntermediateRepresentation/Operand.cs | 594 +++ .../IntermediateRepresentation/OperandKind.cs | 13 + .../IntermediateRepresentation/OperandType.cs | 65 + .../IntermediateRepresentation/Operation.cs | 376 ++ .../IntermediateRepresentation/PhiOperation.cs | 37 + .../IntermediateRepresentation/Register.cs | 43 + .../IntermediateRepresentation/RegisterType.cs | 10 + src/ARMeilleure/Memory/IJitMemoryAllocator.cs | 10 + src/ARMeilleure/Memory/IJitMemoryBlock.cs | 14 + src/ARMeilleure/Memory/IMemoryManager.cs | 77 + src/ARMeilleure/Memory/InvalidAccessException.cs | 23 + src/ARMeilleure/Memory/MemoryManagerType.cs | 41 + src/ARMeilleure/Memory/ReservedRegion.cs | 58 + src/ARMeilleure/Native/JitSupportDarwin.cs | 13 + .../Native/libs/libarmeilleure-jitsupport.dylib | Bin 0 -> 33564 bytes src/ARMeilleure/Native/macos_jit_support/Makefile | 8 + src/ARMeilleure/Native/macos_jit_support/support.c | 14 + src/ARMeilleure/Optimizations.cs | 68 + src/ARMeilleure/Signal/NativeSignalHandler.cs | 422 ++ src/ARMeilleure/Signal/TestMethods.cs | 84 + .../Signal/UnixSignalHandlerRegistration.cs | 83 + .../Signal/WindowsPartialUnmapHandler.cs | 186 + .../Signal/WindowsSignalHandlerRegistration.cs | 44 + src/ARMeilleure/State/Aarch32Mode.cs | 15 + src/ARMeilleure/State/ExceptionCallback.cs | 5 + src/ARMeilleure/State/ExecutionContext.cs | 173 + src/ARMeilleure/State/ExecutionMode.cs | 9 + src/ARMeilleure/State/FPCR.cs | 22 + src/ARMeilleure/State/FPException.cs | 12 + src/ARMeilleure/State/FPRoundingMode.cs | 11 + src/ARMeilleure/State/FPSCR.cs | 15 + src/ARMeilleure/State/FPSR.cs | 18 + src/ARMeilleure/State/FPState.cs | 31 + src/ARMeilleure/State/FPType.cs | 11 + src/ARMeilleure/State/ICounter.cs | 18 + src/ARMeilleure/State/NativeContext.cs | 269 + src/ARMeilleure/State/PState.cs | 17 + src/ARMeilleure/State/RegisterAlias.cs | 42 + src/ARMeilleure/State/RegisterConsts.cs | 15 + src/ARMeilleure/State/V128.cs | 312 ++ src/ARMeilleure/Statistics.cs | 94 + src/ARMeilleure/Translation/ArmEmitterContext.cs | 282 + src/ARMeilleure/Translation/Cache/CacheEntry.cs | 26 + .../Translation/Cache/CacheMemoryAllocator.cs | 96 + src/ARMeilleure/Translation/Cache/JitCache.cs | 198 + .../Translation/Cache/JitCacheInvalidation.cs | 79 + .../Translation/Cache/JitUnwindWindows.cs | 189 + src/ARMeilleure/Translation/Compiler.cs | 68 + src/ARMeilleure/Translation/CompilerContext.cs | 26 + src/ARMeilleure/Translation/CompilerOptions.cs | 17 + src/ARMeilleure/Translation/ControlFlowGraph.cs | 155 + src/ARMeilleure/Translation/DelegateHelper.cs | 104 + src/ARMeilleure/Translation/DelegateInfo.cs | 19 + src/ARMeilleure/Translation/Delegates.cs | 261 + src/ARMeilleure/Translation/DispatcherFunction.cs | 7 + src/ARMeilleure/Translation/Dominance.cs | 95 + src/ARMeilleure/Translation/EmitterContext.cs | 680 +++ src/ARMeilleure/Translation/GuestFunction.cs | 6 + src/ARMeilleure/Translation/IntervalTree.cs | 745 +++ src/ARMeilleure/Translation/PTC/EncodingCache.cs | 9 + src/ARMeilleure/Translation/PTC/IPtcLoadState.cs | 10 + src/ARMeilleure/Translation/PTC/Ptc.cs | 1131 ++++ src/ARMeilleure/Translation/PTC/PtcFormatter.cs | 179 + src/ARMeilleure/Translation/PTC/PtcLoadingState.cs | 9 + src/ARMeilleure/Translation/PTC/PtcProfiler.cs | 421 ++ src/ARMeilleure/Translation/PTC/PtcState.cs | 10 + src/ARMeilleure/Translation/RegisterToLocal.cs | 52 + src/ARMeilleure/Translation/RegisterUsage.cs | 394 ++ src/ARMeilleure/Translation/RejitRequest.cs | 16 + src/ARMeilleure/Translation/SsaConstruction.cs | 289 ++ src/ARMeilleure/Translation/SsaDeconstruction.cs | 48 + src/ARMeilleure/Translation/TranslatedFunction.cs | 34 + src/ARMeilleure/Translation/Translator.cs | 576 +++ src/ARMeilleure/Translation/TranslatorCache.cs | 95 + src/ARMeilleure/Translation/TranslatorQueue.cs | 121 + src/ARMeilleure/Translation/TranslatorStubs.cs | 312 ++ .../Translation/TranslatorTestMethods.cs | 148 + .../OpenALAudioBuffer.cs | 9 + .../OpenALHardwareDeviceDriver.cs | 167 + .../OpenALHardwareDeviceSession.cs | 212 + .../Ryujinx.Audio.Backends.OpenAL.csproj | 15 + .../Ryujinx.Audio.Backends.SDL2.csproj | 13 + src/Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs | 16 + .../SDL2HardwareDeviceDriver.cs | 176 + .../SDL2HardwareDeviceSession.cs | 230 + .../Native/SoundIo.cs | 178 + .../Native/SoundIoBackend.cs | 13 + .../Native/SoundIoChannelId.cs | 75 + .../Native/SoundIoContext.cs | 107 + .../Native/SoundIoDeviceAim.cs | 8 + .../Native/SoundIoDeviceContext.cs | 49 + .../Native/SoundIoError.cs | 22 + .../Native/SoundIoException.cs | 11 + .../Native/SoundIoFormat.cs | 25 + .../Native/SoundIoOutStreamContext.cs | 164 + .../Native/libsoundio/libs/libsoundio.dll | Bin 0 -> 85504 bytes .../Native/libsoundio/libs/libsoundio.dylib | Bin 0 -> 54976 bytes .../Native/libsoundio/libs/libsoundio.so | Bin 0 -> 88584 bytes .../Ryujinx.Audio.Backends.SoundIo.csproj | 28 + .../SoundIoAudioBuffer.cs | 16 + .../SoundIoHardwareDeviceDriver.cs | 247 + .../SoundIoHardwareDeviceSession.cs | 438 ++ src/Ryujinx.Audio/AudioManager.cs | 132 + src/Ryujinx.Audio/Backends/Common/BackendHelper.cs | 26 + .../Backends/Common/DynamicRingBuffer.cs | 166 + .../Common/HardwareDeviceSessionOutputBase.cs | 79 + .../CompatLayer/CompatLayerHardwareDeviceDriver.cs | 186 + .../CompatLayerHardwareDeviceSession.cs | 162 + .../Backends/CompatLayer/Downmixing.cs | 125 + .../Backends/Dummy/DummyHardwareDeviceDriver.cs | 89 + .../Dummy/DummyHardwareDeviceSessionInput.cs | 67 + .../Dummy/DummyHardwareDeviceSessionOutput.cs | 62 + src/Ryujinx.Audio/Common/AudioBuffer.cs | 37 + src/Ryujinx.Audio/Common/AudioDeviceSession.cs | 518 ++ src/Ryujinx.Audio/Common/AudioDeviceState.cs | 18 + .../Common/AudioInputConfiguration.cs | 29 + .../Common/AudioOutputConfiguration.cs | 37 + src/Ryujinx.Audio/Common/AudioUserBuffer.cs | 36 + src/Ryujinx.Audio/Common/SampleFormat.cs | 43 + src/Ryujinx.Audio/Constants.cs | 175 + src/Ryujinx.Audio/Input/AudioInputManager.cs | 266 + src/Ryujinx.Audio/Input/AudioInputSystem.cs | 392 ++ .../Integration/HardwareDeviceImpl.cs | 75 + src/Ryujinx.Audio/Integration/IHardwareDevice.cs | 55 + .../Integration/IHardwareDeviceDriver.cs | 36 + .../Integration/IHardwareDeviceSession.cs | 28 + src/Ryujinx.Audio/Integration/IWritableEvent.cs | 18 + src/Ryujinx.Audio/Output/AudioOutputManager.cs | 296 ++ src/Ryujinx.Audio/Output/AudioOutputSystem.cs | 365 ++ .../Renderer/Common/AuxiliaryBufferAddresses.cs | 13 + .../Renderer/Common/BehaviourParameter.cs | 50 + src/Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs | 150 + src/Ryujinx.Audio/Renderer/Common/EffectType.cs | 58 + .../Renderer/Common/MemoryPoolUserState.cs | 43 + src/Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs | 28 + src/Ryujinx.Audio/Renderer/Common/NodeIdType.cs | 33 + src/Ryujinx.Audio/Renderer/Common/NodeStates.cs | 229 + .../Renderer/Common/PerformanceDetailType.cs | 20 + .../Renderer/Common/PerformanceEntryType.cs | 11 + src/Ryujinx.Audio/Renderer/Common/PlayState.cs | 23 + .../Renderer/Common/ReverbEarlyMode.cs | 33 + .../Renderer/Common/ReverbLateMode.cs | 38 + src/Ryujinx.Audio/Renderer/Common/SinkType.cs | 23 + .../Renderer/Common/UpdateDataHeader.cs | 33 + .../Renderer/Common/VoiceUpdateState.cs | 104 + src/Ryujinx.Audio/Renderer/Common/WaveBuffer.cs | 82 + .../Renderer/Common/WorkBufferAllocator.cs | 61 + src/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs | 89 + .../Renderer/Device/VirtualDeviceSession.cs | 27 + .../Device/VirtualDeviceSessionRegistry.cs | 62 + src/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs | 216 + src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs | 276 + .../Renderer/Dsp/BiquadFilterHelper.cs | 83 + .../Dsp/Command/AdpcmDataSourceCommandVersion1.cs | 75 + .../Renderer/Dsp/Command/AuxiliaryBufferCommand.cs | 173 + .../Renderer/Dsp/Command/BiquadFilterCommand.cs | 51 + .../Renderer/Dsp/Command/CaptureBufferCommand.cs | 136 + .../Dsp/Command/CircularBufferSinkCommand.cs | 76 + .../Renderer/Dsp/Command/ClearMixBufferCommand.cs | 24 + .../Renderer/Dsp/Command/CommandList.cs | 155 + .../Renderer/Dsp/Command/CommandType.cs | 37 + .../Renderer/Dsp/Command/CompressorCommand.cs | 173 + .../Renderer/Dsp/Command/CopyMixBufferCommand.cs | 30 + .../Dsp/Command/DataSourceVersion2Command.cs | 108 + .../Renderer/Dsp/Command/DelayCommand.cs | 280 + .../Dsp/Command/DepopForMixBuffersCommand.cs | 92 + .../Renderer/Dsp/Command/DepopPrepareCommand.cs | 57 + .../Renderer/Dsp/Command/DeviceSinkCommand.cs | 91 + .../Dsp/Command/DownMixSurroundToStereoCommand.cs | 68 + .../Dsp/Command/GroupedBiquadFilterCommand.cs | 62 + src/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs | 20 + .../Renderer/Dsp/Command/LimiterCommandVersion1.cs | 144 + .../Renderer/Dsp/Command/LimiterCommandVersion2.cs | 163 + .../Renderer/Dsp/Command/MixCommand.cs | 137 + .../Renderer/Dsp/Command/MixRampCommand.cs | 68 + .../Renderer/Dsp/Command/MixRampGroupedCommand.cs | 91 + .../Command/PcmFloatDataSourceCommandVersion1.cs | 74 + .../Command/PcmInt16DataSourceCommandVersion1.cs | 74 + .../Renderer/Dsp/Command/PerformanceCommand.cs | 47 + .../Renderer/Dsp/Command/Reverb3dCommand.cs | 254 + .../Renderer/Dsp/Command/ReverbCommand.cs | 279 + .../Renderer/Dsp/Command/UpsampleCommand.cs | 70 + .../Renderer/Dsp/Command/VolumeCommand.cs | 137 + .../Renderer/Dsp/Command/VolumeRampCommand.cs | 56 + src/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs | 466 ++ .../Renderer/Dsp/Effect/DecayDelay.cs | 52 + src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLine.cs | 78 + .../Renderer/Dsp/Effect/DelayLineReverb3d.cs | 76 + .../Dsp/Effect/ExponentialMovingAverage.cs | 26 + .../Renderer/Dsp/Effect/IDelayLine.cs | 37 + src/Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs | 39 + .../Renderer/Dsp/FloatingPointHelper.cs | 115 + src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs | 130 + src/Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs | 604 +++ .../Renderer/Dsp/State/AdpcmLoopContext.cs | 12 + .../Renderer/Dsp/State/AuxiliaryBufferHeader.cs | 74 + .../Renderer/Dsp/State/BiquadFilterState.cs | 13 + .../Renderer/Dsp/State/CompressorState.cs | 51 + src/Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs | 67 + .../Renderer/Dsp/State/LimiterState.cs | 31 + .../Renderer/Dsp/State/Reverb3dState.cs | 119 + .../Renderer/Dsp/State/ReverbState.cs | 204 + src/Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs | 192 + .../Parameter/AudioRendererConfiguration.cs | 99 + .../Parameter/BehaviourErrorInfoOutStatus.cs | 30 + .../Renderer/Parameter/BiquadFilterParameter.cs | 34 + .../Parameter/Effect/AuxiliaryBufferParameter.cs | 84 + .../Effect/BiquadFilterEffectParameter.cs | 44 + .../Parameter/Effect/BufferMixerParameter.cs | 32 + .../Parameter/Effect/CompressorParameter.cs | 115 + .../Renderer/Parameter/Effect/DelayParameter.cs | 101 + .../Renderer/Parameter/Effect/LimiterParameter.cs | 138 + .../Renderer/Parameter/Effect/LimiterStatistics.cs | 31 + .../Renderer/Parameter/Effect/Reverb3dParameter.cs | 127 + .../Renderer/Parameter/Effect/ReverbParameter.cs | 119 + .../Parameter/EffectInParameterVersion1.cs | 97 + .../Parameter/EffectInParameterVersion2.cs | 97 + .../Renderer/Parameter/EffectOutStatusVersion1.cs | 23 + .../Renderer/Parameter/EffectOutStatusVersion2.cs | 28 + .../Renderer/Parameter/EffectResultState.cs | 26 + .../Renderer/Parameter/EffectState.cs | 18 + .../Renderer/Parameter/IEffectInParameter.cs | 53 + .../Renderer/Parameter/IEffectOutStatus.cs | 13 + .../Renderer/Parameter/MemoryPoolInParameter.cs | 33 + .../Renderer/Parameter/MemoryPoolOutStatus.cs | 22 + .../Parameter/MixInParameterDirtyOnlyUpdate.cs | 27 + .../Renderer/Parameter/MixParameter.cs | 95 + .../Performance/PerformanceInParameter.cs | 21 + .../Parameter/Performance/PerformanceOutStatus.cs | 21 + .../Renderer/Parameter/RendererInfoOutStatus.cs | 21 + .../Parameter/Sink/CircularBufferParameter.cs | 62 + .../Renderer/Parameter/Sink/DeviceParameter.cs | 58 + .../Renderer/Parameter/SinkInParameter.cs | 53 + .../Renderer/Parameter/SinkOutStatus.cs | 26 + .../Parameter/SplitterDestinationInParameter.cs | 67 + .../Renderer/Parameter/SplitterInParameter.cs | 46 + .../Parameter/SplitterInParameterHeader.cs | 45 + .../Parameter/VoiceChannelResourceInParameter.cs | 28 + .../Renderer/Parameter/VoiceInParameter.cs | 344 ++ .../Renderer/Parameter/VoiceOutStatus.cs | 35 + .../Renderer/Server/AudioRenderSystem.cs | 893 ++++ .../Renderer/Server/AudioRendererManager.cs | 405 ++ .../Renderer/Server/BehaviourContext.cs | 453 ++ src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs | 568 +++ .../Renderer/Server/CommandGenerator.cs | 1028 ++++ .../CommandProcessingTimeEstimatorVersion1.cs | 188 + .../CommandProcessingTimeEstimatorVersion2.cs | 552 ++ .../CommandProcessingTimeEstimatorVersion3.cs | 756 +++ .../CommandProcessingTimeEstimatorVersion4.cs | 47 + .../CommandProcessingTimeEstimatorVersion5.cs | 310 ++ .../Server/Effect/AuxiliaryBufferEffect.cs | 85 + .../Renderer/Server/Effect/BaseEffect.cs | 272 + .../Renderer/Server/Effect/BiquadFilterEffect.cs | 67 + .../Renderer/Server/Effect/BufferMixEffect.cs | 49 + .../Renderer/Server/Effect/CaptureBufferEffect.cs | 82 + .../Renderer/Server/Effect/CompressorEffect.cs | 67 + .../Renderer/Server/Effect/DelayEffect.cs | 93 + .../Renderer/Server/Effect/EffectContext.cs | 123 + .../Renderer/Server/Effect/LimiterEffect.cs | 95 + .../Renderer/Server/Effect/Reverb3dEffect.cs | 92 + .../Renderer/Server/Effect/ReverbEffect.cs | 95 + .../Renderer/Server/Effect/UsageState.cs | 28 + .../Server/ICommandProcessingTimeEstimator.cs | 40 + .../Renderer/Server/MemoryPool/AddressInfo.cs | 133 + .../Renderer/Server/MemoryPool/MemoryPoolState.cs | 130 + .../Renderer/Server/MemoryPool/PoolMapper.cs | 366 ++ .../Renderer/Server/Mix/MixContext.cs | 259 + src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs | 313 ++ .../Server/Performance/IPerformanceDetailEntry.cs | 52 + .../Server/Performance/IPerformanceEntry.cs | 46 + .../Server/Performance/IPerformanceHeader.cs | 80 + .../Performance/PerformanceDetailVersion1.cs | 72 + .../Performance/PerformanceDetailVersion2.cs | 72 + .../Performance/PerformanceEntryAddresses.cs | 56 + .../Server/Performance/PerformanceEntryVersion1.cs | 62 + .../Server/Performance/PerformanceEntryVersion2.cs | 62 + .../Performance/PerformanceFrameHeaderVersion1.cs | 101 + .../Performance/PerformanceFrameHeaderVersion2.cs | 117 + .../Server/Performance/PerformanceManager.cs | 106 + .../Performance/PerformanceManagerGeneric.cs | 304 ++ .../Renderer/Server/RendererSystemContext.cs | 48 + src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs | 102 + .../Renderer/Server/Sink/CircularBufferSink.cs | 109 + .../Renderer/Server/Sink/DeviceSink.cs | 75 + .../Renderer/Server/Sink/SinkContext.cs | 56 + .../Renderer/Server/Splitter/SplitterContext.cs | 303 ++ .../Server/Splitter/SplitterDestination.cs | 193 + .../Renderer/Server/Splitter/SplitterState.cs | 221 + src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs | 643 +++ .../Server/Types/AudioRendererExecutionMode.cs | 19 + .../Server/Types/AudioRendererRenderingDevice.cs | 24 + .../Renderer/Server/Types/PlayState.cs | 39 + .../Server/Upsampler/UpsamplerBufferState.cs | 14 + .../Renderer/Server/Upsampler/UpsamplerManager.cs | 84 + .../Renderer/Server/Upsampler/UpsamplerState.cs | 68 + .../Renderer/Server/Voice/VoiceChannelResource.cs | 40 + .../Renderer/Server/Voice/VoiceContext.cs | 149 + .../Renderer/Server/Voice/VoiceState.cs | 699 +++ .../Renderer/Server/Voice/WaveBuffer.cs | 104 + .../Renderer/Utils/AudioProcessorMemoryManager.cs | 57 + src/Ryujinx.Audio/Renderer/Utils/BitArray.cs | 103 + .../Renderer/Utils/FileHardwareDevice.cs | 99 + src/Ryujinx.Audio/Renderer/Utils/Mailbox.cs | 55 + src/Ryujinx.Audio/Renderer/Utils/Math/Matrix2x2.cs | 71 + src/Ryujinx.Audio/Renderer/Utils/Math/Matrix6x6.cs | 97 + .../Renderer/Utils/Math/MatrixHelper.cs | 45 + src/Ryujinx.Audio/Renderer/Utils/Math/Vector6.cs | 56 + src/Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs | 171 + .../Renderer/Utils/SpanMemoryManager.cs | 43 + .../Renderer/Utils/SplitterHardwareDevice.cs | 58 + src/Ryujinx.Audio/ResultCode.cs | 22 + src/Ryujinx.Audio/Ryujinx.Audio.csproj | 14 + src/Ryujinx.Ava/App.axaml | 9 + src/Ryujinx.Ava/App.axaml.cs | 154 + src/Ryujinx.Ava/AppHost.cs | 1136 +++++ src/Ryujinx.Ava/Assets/Fonts/SegoeFluentIcons.ttf | Bin 0 -> 408752 bytes src/Ryujinx.Ava/Assets/Locales/de_DE.json | 614 +++ src/Ryujinx.Ava/Assets/Locales/el_GR.json | 614 +++ src/Ryujinx.Ava/Assets/Locales/en_US.json | 645 +++ src/Ryujinx.Ava/Assets/Locales/es_ES.json | 614 +++ src/Ryujinx.Ava/Assets/Locales/fr_FR.json | 614 +++ src/Ryujinx.Ava/Assets/Locales/it_IT.json | 614 +++ src/Ryujinx.Ava/Assets/Locales/ja_JP.json | 614 +++ src/Ryujinx.Ava/Assets/Locales/ko_KR.json | 614 +++ src/Ryujinx.Ava/Assets/Locales/pl_PL.json | 614 +++ src/Ryujinx.Ava/Assets/Locales/pt_BR.json | 614 +++ src/Ryujinx.Ava/Assets/Locales/ru_RU.json | 614 +++ src/Ryujinx.Ava/Assets/Locales/tr_TR.json | 614 +++ src/Ryujinx.Ava/Assets/Locales/uk_UA.json | 614 +++ src/Ryujinx.Ava/Assets/Locales/zh_CN.json | 614 +++ src/Ryujinx.Ava/Assets/Locales/zh_TW.json | 614 +++ src/Ryujinx.Ava/Assets/Styles/BaseDark.xaml | 65 + src/Ryujinx.Ava/Assets/Styles/BaseLight.xaml | 57 + src/Ryujinx.Ava/Assets/Styles/Styles.xaml | 323 ++ src/Ryujinx.Ava/Common/ApplicationHelper.cs | 411 ++ src/Ryujinx.Ava/Common/ApplicationSort.cs | 15 + src/Ryujinx.Ava/Common/KeyboardHotkeyState.cs | 16 + src/Ryujinx.Ava/Common/Locale/LocaleExtension.cs | 30 + src/Ryujinx.Ava/Common/Locale/LocaleManager.cs | 146 + src/Ryujinx.Ava/Input/AvaloniaKeyboard.cs | 201 + src/Ryujinx.Ava/Input/AvaloniaKeyboardDriver.cs | 115 + .../Input/AvaloniaKeyboardMappingHelper.cs | 185 + src/Ryujinx.Ava/Input/AvaloniaMouse.cs | 87 + src/Ryujinx.Ava/Input/AvaloniaMouseDriver.cs | 161 + src/Ryujinx.Ava/Modules/Updater/Updater.cs | 773 +++ src/Ryujinx.Ava/Program.cs | 229 + src/Ryujinx.Ava/Ryujinx.Ava.csproj | 194 + src/Ryujinx.Ava/Ryujinx.ico | Bin 0 -> 108122 bytes src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs | 197 + .../UI/Applet/AvaloniaDynamicTextInputHandler.cs | 164 + src/Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs | 43 + src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml | 52 + .../UI/Applet/ErrorAppletWindow.axaml.cs | 80 + src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml | 65 + .../UI/Applet/SwkbdAppletDialog.axaml.cs | 148 + src/Ryujinx.Ava/UI/Controls/GameGridView.axaml | 177 + src/Ryujinx.Ava/UI/Controls/GameGridView.axaml.cs | 57 + src/Ryujinx.Ava/UI/Controls/GameListView.axaml | 233 + src/Ryujinx.Ava/UI/Controls/GameListView.axaml.cs | 57 + .../UI/Controls/NavigationDialogHost.axaml | 17 + .../UI/Controls/NavigationDialogHost.axaml.cs | 218 + src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml | 42 + .../UI/Controls/UpdateWaitWindow.axaml.cs | 31 + .../UI/Helpers/ApplicationOpenedEventArgs.cs | 16 + .../UI/Helpers/BitmapArrayValueConverter.cs | 35 + src/Ryujinx.Ava/UI/Helpers/ButtonKeyAssigner.cs | 118 + src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs | 407 ++ src/Ryujinx.Ava/UI/Helpers/Glyph.cs | 9 + src/Ryujinx.Ava/UI/Helpers/GlyphValueConverter.cs | 49 + src/Ryujinx.Ava/UI/Helpers/HotKeyControl.cs | 52 + src/Ryujinx.Ava/UI/Helpers/KeyValueConverter.cs | 46 + src/Ryujinx.Ava/UI/Helpers/LoggerAdapter.cs | 115 + src/Ryujinx.Ava/UI/Helpers/MiniCommand.cs | 71 + src/Ryujinx.Ava/UI/Helpers/NotificationHelper.cs | 65 + src/Ryujinx.Ava/UI/Helpers/OffscreenTextBox.cs | 40 + src/Ryujinx.Ava/UI/Helpers/UserErrorDialog.cs | 91 + src/Ryujinx.Ava/UI/Helpers/UserResult.cs | 12 + src/Ryujinx.Ava/UI/Helpers/Win32NativeInterop.cs | 123 + src/Ryujinx.Ava/UI/Models/CheatModel.cs | 40 + src/Ryujinx.Ava/UI/Models/CheatsList.cs | 51 + src/Ryujinx.Ava/UI/Models/ControllerModel.cs | 6 + src/Ryujinx.Ava/UI/Models/DeviceType.cs | 9 + .../UI/Models/DownloadableContentModel.cs | 35 + .../UI/Models/Generic/LastPlayedSortComparer.cs | 33 + src/Ryujinx.Ava/UI/Models/InputConfiguration.cs | 456 ++ src/Ryujinx.Ava/UI/Models/PlayerModel.cs | 6 + src/Ryujinx.Ava/UI/Models/ProfileImageModel.cs | 32 + src/Ryujinx.Ava/UI/Models/SaveModel.cs | 112 + .../UI/Models/StatusUpdatedEventArgs.cs | 28 + src/Ryujinx.Ava/UI/Models/TempProfile.cs | 61 + src/Ryujinx.Ava/UI/Models/TimeZone.cs | 16 + src/Ryujinx.Ava/UI/Models/TitleUpdateModel.cs | 19 + src/Ryujinx.Ava/UI/Models/UserProfile.cs | 103 + src/Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs | 288 ++ .../UI/Renderer/EmbeddedWindowOpenGL.cs | 87 + .../UI/Renderer/EmbeddedWindowVulkan.cs | 42 + .../UI/Renderer/OpenTKBindingsContext.cs | 20 + src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml | 11 + src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs | 68 + src/Ryujinx.Ava/UI/Renderer/SPBOpenGLContext.cs | 47 + .../UI/ViewModels/AboutWindowViewModel.cs | 133 + .../UI/ViewModels/AmiiboWindowViewModel.cs | 467 ++ .../UI/ViewModels/AvatarProfileViewModel.cs | 363 ++ src/Ryujinx.Ava/UI/ViewModels/BaseModel.cs | 15 + .../UI/ViewModels/ControllerSettingsViewModel.cs | 899 ++++ .../DownloadableContentManagerViewModel.cs | 338 ++ .../UI/ViewModels/MainWindowViewModel.cs | 1907 +++++++ src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs | 585 +++ .../UI/ViewModels/TitleUpdateViewModel.cs | 252 + .../UserFirmwareAvatarSelectorViewModel.cs | 230 + .../UserProfileImageSelectorViewModel.cs | 18 + .../UI/ViewModels/UserProfileViewModel.cs | 25 + .../UI/ViewModels/UserSaveManagerViewModel.cs | 120 + .../UI/Views/Main/MainMenuBarView.axaml | 164 + .../UI/Views/Main/MainMenuBarView.axaml.cs | 236 + .../UI/Views/Main/MainStatusBarView.axaml | 232 + .../UI/Views/Main/MainStatusBarView.axaml.cs | 52 + .../UI/Views/Main/MainViewControls.axaml | 175 + .../UI/Views/Main/MainViewControls.axaml.cs | 54 + .../UI/Views/Settings/SettingsAudioView.axaml | 81 + .../UI/Views/Settings/SettingsAudioView.axaml.cs | 12 + .../UI/Views/Settings/SettingsCPUView.axaml | 78 + .../UI/Views/Settings/SettingsCPUView.axaml.cs | 12 + .../UI/Views/Settings/SettingsGraphicsView.axaml | 296 ++ .../Views/Settings/SettingsGraphicsView.axaml.cs | 12 + .../UI/Views/Settings/SettingsHotkeysView.axaml | 104 + .../UI/Views/Settings/SettingsHotkeysView.axaml.cs | 81 + .../UI/Views/Settings/SettingsInputView.axaml | 46 + .../UI/Views/Settings/SettingsInputView.axaml.cs | 17 + .../UI/Views/Settings/SettingsLoggingView.axaml | 121 + .../UI/Views/Settings/SettingsLoggingView.axaml.cs | 12 + .../UI/Views/Settings/SettingsNetworkView.axaml | 46 + .../UI/Views/Settings/SettingsNetworkView.axaml.cs | 12 + .../UI/Views/Settings/SettingsSystemView.axaml | 195 + .../UI/Views/Settings/SettingsSystemView.axaml.cs | 52 + .../UI/Views/Settings/SettingsUIView.axaml | 156 + .../UI/Views/Settings/SettingsUIView.axaml.cs | 82 + src/Ryujinx.Ava/UI/Views/User/UserEditorView.axaml | 123 + .../UI/Views/User/UserEditorView.axaml.cs | 165 + .../User/UserFirmwareAvatarSelectorView.axaml | 114 + .../User/UserFirmwareAvatarSelectorView.axaml.cs | 88 + .../Views/User/UserProfileImageSelectorView.axaml | 63 + .../User/UserProfileImageSelectorView.axaml.cs | 124 + .../UI/Views/User/UserRecovererView.axaml | 83 + .../UI/Views/User/UserRecovererView.axaml.cs | 51 + .../UI/Views/User/UserSaveManagerView.axaml | 215 + .../UI/Views/User/UserSaveManagerView.axaml.cs | 147 + .../UI/Views/User/UserSelectorView.axaml | 165 + .../UI/Views/User/UserSelectorView.axaml.cs | 128 + src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml | 247 + src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml.cs | 62 + src/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml | 74 + src/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml.cs | 59 + src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml | 106 + src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs | 119 + .../UI/Windows/ContentDialogOverlayWindow.axaml | 25 + .../UI/Windows/ContentDialogOverlayWindow.axaml.cs | 25 + .../UI/Windows/ControllerSettingsWindow.axaml | 1169 +++++ .../UI/Windows/ControllerSettingsWindow.axaml.cs | 181 + .../Windows/DownloadableContentManagerWindow.axaml | 194 + .../DownloadableContentManagerWindow.axaml.cs | 115 + src/Ryujinx.Ava/UI/Windows/IconColorPicker.cs | 192 + src/Ryujinx.Ava/UI/Windows/MainWindow.axaml | 206 + src/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs | 441 ++ .../UI/Windows/MotionSettingsWindow.axaml | 141 + .../UI/Windows/MotionSettingsWindow.axaml.cs | 71 + .../UI/Windows/RumbleSettingsWindow.axaml | 57 + .../UI/Windows/RumbleSettingsWindow.axaml.cs | 57 + src/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml | 128 + src/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs | 103 + src/Ryujinx.Ava/UI/Windows/StyleableWindow.cs | 39 + src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml | 135 + .../UI/Windows/TitleUpdateWindow.axaml.cs | 96 + src/Ryujinx.Common/AsyncWorkQueue.cs | 100 + src/Ryujinx.Common/Collections/IntervalTree.cs | 499 ++ .../Collections/IntrusiveRedBlackTree.cs | 285 ++ .../Collections/IntrusiveRedBlackTreeImpl.cs | 354 ++ .../Collections/IntrusiveRedBlackTreeNode.cs | 16 + src/Ryujinx.Common/Collections/TreeDictionary.cs | 617 +++ src/Ryujinx.Common/Configuration/AntiAliasing.cs | 16 + src/Ryujinx.Common/Configuration/AppDataManager.cs | 149 + .../Configuration/AspectRatioExtensions.cs | 63 + .../Configuration/BackendThreading.cs | 13 + .../Configuration/DownloadableContentContainer.cs | 13 + .../DownloadableContentJsonSerializerContext.cs | 11 + .../Configuration/DownloadableContentNca.cs | 14 + .../Configuration/GraphicsBackend.cs | 12 + .../Configuration/GraphicsDebugLevel.cs | 14 + .../Configuration/Hid/Controller/GamepadInputId.cs | 58 + .../Hid/Controller/GenericControllerInputConfig.cs | 82 + .../Hid/Controller/JoyconConfigControllerStick.cs | 11 + .../Motion/CemuHookMotionConfigController.cs | 30 + .../Motion/JsonMotionConfigControllerConverter.cs | 79 + .../Controller/Motion/MotionConfigController.cs | 25 + .../Motion/MotionConfigJsonSerializerContext.cs | 12 + .../Controller/Motion/MotionInputBackendType.cs | 13 + .../Motion/StandardMotionConfigController.cs | 4 + .../Hid/Controller/RumbleConfigController.cs | 20 + .../Controller/StandardControllerInputConfig.cs | 4 + .../Configuration/Hid/Controller/StickInputId.cs | 15 + .../Configuration/Hid/ControllerType.cs | 23 + .../Hid/GenericInputConfigurationCommon.cs | 15 + .../Configuration/Hid/InputBackendType.cs | 13 + .../Configuration/Hid/InputConfig.cs | 41 + .../Hid/InputConfigJsonSerializerContext.cs | 14 + .../Configuration/Hid/JsonInputConfigConverter.cs | 81 + src/Ryujinx.Common/Configuration/Hid/Key.cs | 143 + .../Hid/Keyboard/GenericKeyboardInputConfig.cs | 15 + .../Hid/Keyboard/JoyconConfigKeyboardStick.cs | 11 + .../Hid/Keyboard/StandardKeyboardInputConfig.cs | 4 + .../Configuration/Hid/KeyboardHotkeys.cs | 17 + .../Configuration/Hid/LeftJoyconCommonConfig.cs | 15 + .../Configuration/Hid/PlayerIndex.cs | 22 + .../Configuration/Hid/RightJoyconCommonConfig.cs | 15 + .../Configuration/MemoryManagerMode.cs | 13 + src/Ryujinx.Common/Configuration/ScalingFilter.cs | 13 + .../Configuration/TitleUpdateMetadata.cs | 10 + .../TitleUpdateMetadataJsonSerializerContext.cs | 10 + .../Extensions/BinaryReaderExtensions.cs | 16 + .../Extensions/BinaryWriterExtensions.cs | 28 + src/Ryujinx.Common/Extensions/StreamExtensions.cs | 138 + .../GraphicsDriver/DriverUtilities.cs | 22 + src/Ryujinx.Common/GraphicsDriver/NVAPI/Nvapi.cs | 11 + .../GraphicsDriver/NVAPI/NvapiUnicodeString.cs | 42 + .../GraphicsDriver/NVAPI/NvdrsApplicationV4.cs | 17 + .../GraphicsDriver/NVAPI/NvdrsProfile.cs | 15 + .../GraphicsDriver/NVAPI/NvdrsSetting.cs | 49 + .../GraphicsDriver/NVThreadedOptimization.cs | 163 + src/Ryujinx.Common/Hash128.cs | 48 + .../Logging/Formatters/DefaultLogFormatter.cs | 42 + .../Logging/Formatters/DynamicObjectFormatter.cs | 84 + .../Logging/Formatters/ILogFormatter.cs | 7 + src/Ryujinx.Common/Logging/LogClass.cs | 76 + src/Ryujinx.Common/Logging/LogEventArgs.cs | 23 + src/Ryujinx.Common/Logging/LogEventArgsJson.cs | 30 + .../Logging/LogEventJsonSerializerContext.cs | 9 + src/Ryujinx.Common/Logging/LogLevel.cs | 19 + src/Ryujinx.Common/Logging/Logger.cs | 224 + .../Logging/Targets/AsyncLogTargetWrapper.cs | 79 + .../Logging/Targets/ConsoleLogTarget.cs | 41 + .../Logging/Targets/FileLogTarget.cs | 55 + src/Ryujinx.Common/Logging/Targets/ILogTarget.cs | 11 + .../Logging/Targets/JsonLogTarget.cs | 40 + src/Ryujinx.Common/Memory/ArrayPtr.cs | 123 + src/Ryujinx.Common/Memory/Box.cs | 12 + .../Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs | 51 + src/Ryujinx.Common/Memory/ByteMemoryPool.cs | 108 + src/Ryujinx.Common/Memory/IArray.cs | 21 + src/Ryujinx.Common/Memory/MemoryStreamManager.cs | 99 + .../Memory/PartialUnmaps/NativeReaderWriterLock.cs | 80 + .../Memory/PartialUnmaps/PartialUnmapHelpers.cs | 20 + .../Memory/PartialUnmaps/PartialUnmapState.cs | 163 + .../Memory/PartialUnmaps/ThreadLocalMap.cs | 92 + src/Ryujinx.Common/Memory/Ptr.cs | 68 + src/Ryujinx.Common/Memory/SpanOrArray.cs | 89 + src/Ryujinx.Common/Memory/SpanReader.cs | 56 + src/Ryujinx.Common/Memory/SpanWriter.cs | 45 + src/Ryujinx.Common/Memory/StructArrayHelpers.cs | 654 +++ .../Memory/StructByteArrayHelpers.cs | 77 + src/Ryujinx.Common/PerformanceCounter.cs | 82 + src/Ryujinx.Common/Pools/ObjectPool.cs | 75 + src/Ryujinx.Common/Pools/SharedPools.cs | 17 + src/Ryujinx.Common/Pools/ThreadStaticArray.cs | 20 + src/Ryujinx.Common/ReactiveObject.cs | 61 + src/Ryujinx.Common/ReferenceEqualityComparer.cs | 19 + src/Ryujinx.Common/ReleaseInformation.cs | 60 + src/Ryujinx.Common/Ryujinx.Common.csproj | 15 + src/Ryujinx.Common/SystemInfo/LinuxSystemInfo.cs | 80 + src/Ryujinx.Common/SystemInfo/MacOSSystemInfo.cs | 157 + src/Ryujinx.Common/SystemInfo/SystemInfo.cs | 80 + src/Ryujinx.Common/SystemInfo/WindowsSystemInfo.cs | 89 + src/Ryujinx.Common/SystemInterop/DisplaySleep.cs | 35 + src/Ryujinx.Common/SystemInterop/ForceDpiAware.cs | 96 + src/Ryujinx.Common/SystemInterop/GdiPlusHelper.cs | 76 + src/Ryujinx.Common/SystemInterop/StdErrAdapter.cs | 109 + .../WindowsMultimediaTimerResolution.cs | 114 + src/Ryujinx.Common/Utilities/BitUtils.cs | 60 + src/Ryujinx.Common/Utilities/BitfieldExtensions.cs | 57 + src/Ryujinx.Common/Utilities/Buffers.cs | 59 + src/Ryujinx.Common/Utilities/CommonJsonContext.cs | 11 + src/Ryujinx.Common/Utilities/EmbeddedResources.cs | 148 + src/Ryujinx.Common/Utilities/HexUtils.cs | 90 + src/Ryujinx.Common/Utilities/JsonHelper.cs | 98 + .../Utilities/MessagePackObjectFormatter.cs | 302 ++ src/Ryujinx.Common/Utilities/NetworkHelpers.cs | 66 + src/Ryujinx.Common/Utilities/SpanHelpers.cs | 61 + src/Ryujinx.Common/Utilities/StreamUtils.cs | 31 + .../Utilities/TypedStringEnumConverter.cs | 34 + src/Ryujinx.Common/Utilities/UInt128Utils.cs | 18 + src/Ryujinx.Common/XXHash128.cs | 537 ++ src/Ryujinx.Cpu/AddressSpace.cs | 470 ++ src/Ryujinx.Cpu/AppleHv/Arm/ApFlags.cs | 27 + src/Ryujinx.Cpu/AppleHv/Arm/ExceptionClass.cs | 47 + src/Ryujinx.Cpu/AppleHv/DummyDiskCacheLoadState.cs | 17 + src/Ryujinx.Cpu/AppleHv/HvAddressSpace.cs | 129 + src/Ryujinx.Cpu/AppleHv/HvAddressSpaceRange.cs | 370 ++ src/Ryujinx.Cpu/AppleHv/HvApi.cs | 320 ++ src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs | 47 + src/Ryujinx.Cpu/AppleHv/HvEngine.cs | 20 + src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs | 284 ++ .../AppleHv/HvExecutionContextShadow.cs | 59 + src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs | 196 + src/Ryujinx.Cpu/AppleHv/HvIpaAllocator.cs | 34 + src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocation.cs | 34 + src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocator.cs | 59 + src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs | 947 ++++ src/Ryujinx.Cpu/AppleHv/HvVcpu.cs | 25 + src/Ryujinx.Cpu/AppleHv/HvVcpuPool.cs | 103 + src/Ryujinx.Cpu/AppleHv/HvVm.cs | 68 + src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs | 46 + src/Ryujinx.Cpu/ExceptionCallbacks.cs | 64 + src/Ryujinx.Cpu/ICpuContext.cs | 61 + src/Ryujinx.Cpu/ICpuEngine.cs | 18 + src/Ryujinx.Cpu/IDiskCacheState.cs | 20 + src/Ryujinx.Cpu/IExecutionContext.cs | 112 + src/Ryujinx.Cpu/ITickSource.cs | 31 + src/Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs | 56 + src/Ryujinx.Cpu/Jit/JitCpuContext.cs | 53 + src/Ryujinx.Cpu/Jit/JitDiskCacheLoadState.cs | 38 + src/Ryujinx.Cpu/Jit/JitEngine.cs | 20 + src/Ryujinx.Cpu/Jit/JitExecutionContext.cs | 123 + src/Ryujinx.Cpu/Jit/JitMemoryAllocator.cs | 13 + src/Ryujinx.Cpu/Jit/JitMemoryBlock.cs | 24 + src/Ryujinx.Cpu/Jit/MemoryManager.cs | 704 +++ src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs | 817 +++ src/Ryujinx.Cpu/LoadState.cs | 12 + src/Ryujinx.Cpu/MemoryEhMeilleure.cs | 62 + src/Ryujinx.Cpu/MemoryHelper.cs | 63 + src/Ryujinx.Cpu/MemoryManagerBase.cs | 32 + src/Ryujinx.Cpu/PrivateMemoryAllocation.cs | 41 + src/Ryujinx.Cpu/PrivateMemoryAllocator.cs | 268 + src/Ryujinx.Cpu/Ryujinx.Cpu.csproj | 13 + src/Ryujinx.Cpu/TickSource.cs | 45 + src/Ryujinx.Cpu/Tracking/CpuMultiRegionHandle.cs | 28 + src/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs | 37 + .../Tracking/CpuSmartMultiRegionHandle.cs | 26 + src/Ryujinx.Graphics.Device/DeviceState.cs | 162 + src/Ryujinx.Graphics.Device/IDeviceState.cs | 8 + .../IDeviceStateWithContext.cs | 9 + src/Ryujinx.Graphics.Device/RwCallback.cs | 16 + .../Ryujinx.Graphics.Device.csproj | 7 + src/Ryujinx.Graphics.Device/SizeCalculator.cs | 63 + src/Ryujinx.Graphics.GAL/AddressMode.cs | 14 + .../AdvancedBlendDescriptor.cs | 16 + src/Ryujinx.Graphics.GAL/AdvancedBlendOp.cs | 52 + src/Ryujinx.Graphics.GAL/AdvancedBlendOverlap.cs | 9 + src/Ryujinx.Graphics.GAL/AntiAliasing.cs | 12 + src/Ryujinx.Graphics.GAL/BlendDescriptor.cs | 35 + src/Ryujinx.Graphics.GAL/BlendFactor.cs | 62 + src/Ryujinx.Graphics.GAL/BlendOp.cs | 17 + src/Ryujinx.Graphics.GAL/BufferAssignment.cs | 14 + src/Ryujinx.Graphics.GAL/BufferHandle.cs | 14 + src/Ryujinx.Graphics.GAL/BufferRange.cs | 21 + src/Ryujinx.Graphics.GAL/Capabilities.cs | 140 + src/Ryujinx.Graphics.GAL/ColorF.cs | 4 + src/Ryujinx.Graphics.GAL/CompareMode.cs | 8 + src/Ryujinx.Graphics.GAL/CompareOp.cs | 23 + src/Ryujinx.Graphics.GAL/CounterType.cs | 9 + src/Ryujinx.Graphics.GAL/DepthMode.cs | 8 + src/Ryujinx.Graphics.GAL/DepthStencilMode.cs | 8 + src/Ryujinx.Graphics.GAL/DepthTestDescriptor.cs | 20 + src/Ryujinx.Graphics.GAL/DeviceInfo.cs | 18 + src/Ryujinx.Graphics.GAL/Extents2D.cs | 31 + src/Ryujinx.Graphics.GAL/Extents2DF.cs | 18 + src/Ryujinx.Graphics.GAL/Face.cs | 9 + src/Ryujinx.Graphics.GAL/Format.cs | 667 +++ src/Ryujinx.Graphics.GAL/FrontFace.cs | 8 + src/Ryujinx.Graphics.GAL/HardwareInfo.cs | 14 + src/Ryujinx.Graphics.GAL/ICounterEvent.cs | 13 + src/Ryujinx.Graphics.GAL/IPipeline.cs | 113 + src/Ryujinx.Graphics.GAL/IProgram.cs | 11 + src/Ryujinx.Graphics.GAL/IRenderer.cs | 65 + src/Ryujinx.Graphics.GAL/ISampler.cs | 6 + src/Ryujinx.Graphics.GAL/ITexture.cs | 27 + src/Ryujinx.Graphics.GAL/IWindow.cs | 17 + src/Ryujinx.Graphics.GAL/ImageCrop.cs | 37 + src/Ryujinx.Graphics.GAL/IndexType.cs | 9 + src/Ryujinx.Graphics.GAL/LogicalOp.cs | 22 + src/Ryujinx.Graphics.GAL/MagFilter.cs | 8 + src/Ryujinx.Graphics.GAL/MinFilter.cs | 12 + src/Ryujinx.Graphics.GAL/MultisampleDescriptor.cs | 19 + .../Multithreading/BufferMap.cs | 194 + .../Multithreading/CommandHelper.cs | 149 + .../Multithreading/CommandType.cs | 102 + .../Multithreading/Commands/BarrierCommand.cs | 12 + .../Commands/BeginTransformFeedbackCommand.cs | 18 + .../Commands/Buffer/BufferDisposeCommand.cs | 19 + .../Commands/Buffer/BufferGetDataCommand.cs | 29 + .../Commands/Buffer/BufferSetDataCommand.cs | 27 + .../Multithreading/Commands/ClearBufferCommand.cs | 24 + .../Commands/ClearRenderTargetColorCommand.cs | 26 + .../ClearRenderTargetDepthStencilCommand.cs | 28 + .../Commands/CommandBufferBarrierCommand.cs | 12 + .../Multithreading/Commands/CopyBufferCommand.cs | 26 + .../CounterEvent/CounterEventDisposeCommand.cs | 21 + .../CounterEvent/CounterEventFlushCommand.cs | 21 + .../Commands/DispatchComputeCommand.cs | 22 + .../Multithreading/Commands/DrawCommand.cs | 26 + .../Multithreading/Commands/DrawIndexedCommand.cs | 24 + .../Commands/DrawIndexedIndirectCommand.cs | 18 + .../Commands/DrawIndexedIndirectCountCommand.cs | 29 + .../Multithreading/Commands/DrawIndirectCommand.cs | 18 + .../Commands/DrawIndirectCountCommand.cs | 29 + .../Multithreading/Commands/DrawTextureCommand.cs | 31 + .../Commands/EndHostConditionalRenderingCommand.cs | 12 + .../Commands/EndTransformFeedbackCommand.cs | 12 + .../Multithreading/Commands/IGALCommand.cs | 12 + .../Commands/Program/ProgramCheckLinkCommand.cs | 27 + .../Commands/Program/ProgramDisposeCommand.cs | 21 + .../Commands/Program/ProgramGetBinaryCommand.cs | 25 + .../Commands/Renderer/ActionCommand.cs | 21 + .../Commands/Renderer/CreateBufferCommand.cs | 29 + .../Commands/Renderer/CreateProgramCommand.cs | 28 + .../Commands/Renderer/CreateSamplerCommand.cs | 23 + .../Commands/Renderer/CreateSyncCommand.cs | 22 + .../Commands/Renderer/CreateTextureCommand.cs | 25 + .../Commands/Renderer/GetCapabilitiesCommand.cs | 20 + .../Commands/Renderer/PreFrameCommand.cs | 12 + .../Commands/Renderer/ReportCounterCommand.cs | 30 + .../Commands/Renderer/ResetCounterCommand.cs | 18 + .../Commands/Renderer/UpdateCountersCommand.cs | 12 + .../Commands/Sampler/SamplerDisposeCommand.cs | 21 + .../Multithreading/Commands/SetAlphaTestCommand.cs | 22 + .../Commands/SetBlendStateAdvancedCommand.cs | 18 + .../Commands/SetBlendStateCommand.cs | 20 + .../Multithreading/Commands/SetDepthBiasCommand.cs | 24 + .../Commands/SetDepthClampCommand.cs | 18 + .../Multithreading/Commands/SetDepthModeCommand.cs | 18 + .../Multithreading/Commands/SetDepthTestCommand.cs | 18 + .../Commands/SetFaceCullingCommand.cs | 20 + .../Multithreading/Commands/SetFrontFaceCommand.cs | 18 + .../Multithreading/Commands/SetImageCommand.cs | 25 + .../Commands/SetIndexBufferCommand.cs | 21 + .../Commands/SetLineParametersCommand.cs | 20 + .../Commands/SetLogicOpStateCommand.cs | 20 + .../Commands/SetMultisampleStateCommand.cs | 18 + .../Commands/SetPatchParametersCommand.cs | 25 + .../Commands/SetPointParametersCommand.cs | 24 + .../Commands/SetPolygonModeCommand.cs | 20 + .../Commands/SetPrimitiveRestartCommand.cs | 20 + .../Commands/SetPrimitiveTopologyCommand.cs | 18 + .../Multithreading/Commands/SetProgramCommand.cs | 25 + .../Commands/SetRasterizerDiscardCommand.cs | 18 + .../Commands/SetRenderTargetColorMasksCommand.cs | 23 + .../Commands/SetRenderTargetScaleCommand.cs | 18 + .../Commands/SetRenderTargetsCommand.cs | 24 + .../Multithreading/Commands/SetScissorsCommand.cs | 22 + .../Commands/SetStencilTestCommand.cs | 18 + .../Commands/SetStorageBuffersCommand.cs | 23 + .../Commands/SetTextureAndSamplerCommand.cs | 28 + .../Commands/SetTransformFeedbackBuffersCommand.cs | 23 + .../Commands/SetUniformBuffersCommand.cs | 23 + .../Commands/SetUserClipDistanceCommand.cs | 20 + .../Commands/SetVertexAttribsCommand.cs | 23 + .../Commands/SetVertexBuffersCommand.cs | 23 + .../Multithreading/Commands/SetViewportsCommand.cs | 25 + .../Commands/Texture/TextureCopyToCommand.cs | 28 + .../Commands/Texture/TextureCopyToScaledCommand.cs | 30 + .../Commands/Texture/TextureCopyToSliceCommand.cs | 32 + .../Commands/Texture/TextureCreateViewCommand.cs | 30 + .../Commands/Texture/TextureGetDataCommand.cs | 26 + .../Commands/Texture/TextureGetDataSliceCommand.cs | 30 + .../Commands/Texture/TextureReleaseCommand.cs | 21 + .../Commands/Texture/TextureSetDataCommand.cs | 25 + .../Commands/Texture/TextureSetDataSliceCommand.cs | 29 + .../Texture/TextureSetDataSliceRegionCommand.cs | 31 + .../Commands/Texture/TextureSetStorageCommand.cs | 23 + .../Commands/TextureBarrierCommand.cs | 12 + .../Commands/TextureBarrierTiledCommand.cs | 12 + .../Commands/TryHostConditionalRenderingCommand.cs | 25 + .../TryHostConditionalRenderingFlushCommand.cs | 25 + .../Commands/UpdateRenderScaleCommand.cs | 25 + .../Commands/Window/WindowPresentCommand.cs | 27 + .../Multithreading/Model/CircularSpanPool.cs | 89 + .../Multithreading/Model/ResultBox.cs | 7 + .../Multithreading/Model/SpanRef.cs | 39 + .../Multithreading/Model/TableRef.cs | 22 + .../Multithreading/Resources/ProgramQueue.cs | 107 + .../Resources/Programs/BinaryProgramRequest.cs | 25 + .../Resources/Programs/IProgramRequest.cs | 8 + .../Resources/Programs/SourceProgramRequest.cs | 23 + .../Resources/ThreadedCounterEvent.cs | 80 + .../Multithreading/Resources/ThreadedProgram.cs | 48 + .../Multithreading/Resources/ThreadedSampler.cs | 22 + .../Multithreading/Resources/ThreadedTexture.cs | 141 + src/Ryujinx.Graphics.GAL/Multithreading/SyncMap.cs | 62 + .../Multithreading/ThreadedHelpers.cs | 28 + .../Multithreading/ThreadedPipeline.cs | 380 ++ .../Multithreading/ThreadedRenderer.cs | 488 ++ .../Multithreading/ThreadedWindow.cs | 42 + src/Ryujinx.Graphics.GAL/Origin.cs | 8 + src/Ryujinx.Graphics.GAL/PinnedSpan.cs | 53 + src/Ryujinx.Graphics.GAL/PolygonMode.cs | 9 + src/Ryujinx.Graphics.GAL/PolygonModeMask.cs | 12 + src/Ryujinx.Graphics.GAL/PrimitiveTopology.cs | 21 + src/Ryujinx.Graphics.GAL/ProgramLinkStatus.cs | 9 + src/Ryujinx.Graphics.GAL/ProgramPipelineState.cs | 78 + src/Ryujinx.Graphics.GAL/Rectangle.cs | 18 + .../Ryujinx.Graphics.GAL.csproj | 20 + src/Ryujinx.Graphics.GAL/SamplerCreateInfo.cs | 72 + src/Ryujinx.Graphics.GAL/ScreenCaptureImageInfo.cs | 22 + src/Ryujinx.Graphics.GAL/ShaderBindings.cs | 24 + src/Ryujinx.Graphics.GAL/ShaderInfo.cs | 23 + src/Ryujinx.Graphics.GAL/ShaderSource.cs | 31 + src/Ryujinx.Graphics.GAL/StencilOp.cs | 23 + src/Ryujinx.Graphics.GAL/StencilTestDescriptor.cs | 56 + src/Ryujinx.Graphics.GAL/SupportBufferUpdater.cs | 101 + src/Ryujinx.Graphics.GAL/SwizzleComponent.cs | 12 + src/Ryujinx.Graphics.GAL/Target.cs | 34 + src/Ryujinx.Graphics.GAL/TextureCreateInfo.cs | 164 + src/Ryujinx.Graphics.GAL/TextureReleaseCallback.cs | 4 + src/Ryujinx.Graphics.GAL/UpscaleType.cs | 9 + src/Ryujinx.Graphics.GAL/VertexAttribDescriptor.cs | 4 + src/Ryujinx.Graphics.GAL/VertexBufferDescriptor.cs | 17 + src/Ryujinx.Graphics.GAL/Viewport.cs | 33 + src/Ryujinx.Graphics.GAL/ViewportSwizzle.cs | 16 + src/Ryujinx.Graphics.Gpu/ClassId.cs | 15 + src/Ryujinx.Graphics.Gpu/Constants.cs | 104 + .../Engine/Compute/ComputeClass.cs | 219 + .../Engine/Compute/ComputeClassState.cs | 435 ++ .../Engine/Compute/ComputeQmd.cs | 275 + .../Engine/ConditionalRenderEnabled.cs | 12 + .../Engine/DeviceStateWithShadow.cs | 96 + src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs | 635 +++ .../Engine/Dma/DmaClassState.cs | 271 + src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaTexture.cs | 20 + .../Engine/GPFifo/CompressedMethod.cs | 41 + src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPEntry.cs | 55 + .../Engine/GPFifo/GPFifoClass.cs | 248 + .../Engine/GPFifo/GPFifoClassState.cs | 233 + .../Engine/GPFifo/GPFifoDevice.cs | 262 + .../Engine/GPFifo/GPFifoProcessor.cs | 331 ++ .../Engine/InlineToMemory/InlineToMemoryClass.cs | 273 + .../InlineToMemory/InlineToMemoryClassState.cs | 181 + .../Engine/MME/AluOperation.cs | 15 + .../Engine/MME/AluRegOperation.cs | 18 + .../Engine/MME/AssignmentOperation.cs | 17 + src/Ryujinx.Graphics.Gpu/Engine/MME/IMacroEE.cs | 52 + src/Ryujinx.Graphics.Gpu/Engine/MME/Macro.cs | 101 + src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs | 341 ++ .../Engine/MME/MacroHLEFunctionName.cs | 16 + .../Engine/MME/MacroHLETable.cs | 113 + .../Engine/MME/MacroInterpreter.cs | 400 ++ src/Ryujinx.Graphics.Gpu/Engine/MME/MacroJit.cs | 39 + .../Engine/MME/MacroJitCompiler.cs | 517 ++ .../Engine/MME/MacroJitContext.cs | 55 + .../Engine/MmeShadowScratch.cs | 18 + .../Engine/SetMmeShadowRamControlMode.cs | 13 + src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs | 111 + .../Threed/Blender/AdvancedBlendFunctions.cs | 4226 +++++++++++++++ .../Engine/Threed/Blender/AdvancedBlendManager.cs | 115 + .../Threed/Blender/AdvancedBlendPreGenTable.cs | 273 + .../Engine/Threed/Blender/AdvancedBlendUcode.cs | 126 + .../Engine/Threed/Blender/UcodeAssembler.cs | 305 ++ .../Engine/Threed/ConditionalRendering.cs | 130 + .../Engine/Threed/ConstantBufferUpdater.cs | 183 + .../Engine/Threed/DrawManager.cs | 856 ++++ .../Engine/Threed/DrawState.cs | 65 + .../Engine/Threed/IbStreamer.cs | 194 + .../Engine/Threed/IndirectDrawType.cs | 38 + .../Engine/Threed/RenderTargetUpdateFlags.cs | 41 + .../Engine/Threed/SemaphoreUpdater.cs | 190 + .../Engine/Threed/SpecializationStateUpdater.cs | 346 ++ .../Engine/Threed/StateUpdateTracker.cs | 177 + .../Engine/Threed/StateUpdater.cs | 1448 ++++++ .../Engine/Threed/ThreedClass.cs | 620 +++ .../Engine/Threed/ThreedClassState.cs | 1048 ++++ src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClass.cs | 379 ++ .../Engine/Twod/TwodClassState.cs | 816 +++ .../Engine/Twod/TwodTexture.cs | 22 + src/Ryujinx.Graphics.Gpu/Engine/Types/Boolean32.cs | 17 + .../Engine/Types/ColorFormat.cs | 165 + src/Ryujinx.Graphics.Gpu/Engine/Types/GpuVa.cs | 22 + .../Engine/Types/MemoryLayout.cs | 37 + .../Engine/Types/PrimitiveType.cs | 99 + .../Engine/Types/SamplerIndex.cs | 11 + .../Engine/Types/SbDescriptor.cs | 20 + .../Engine/Types/ZetaFormat.cs | 42 + src/Ryujinx.Graphics.Gpu/GpuChannel.cs | 148 + src/Ryujinx.Graphics.Gpu/GpuContext.cs | 397 ++ src/Ryujinx.Graphics.Gpu/GraphicsConfig.cs | 70 + src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs | 256 + src/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs | 72 + src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs | 578 +++ .../Image/ITextureDescriptor.cs | 10 + src/Ryujinx.Graphics.Gpu/Image/Pool.cs | 222 + src/Ryujinx.Graphics.Gpu/Image/PoolCache.cs | 129 + src/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs | 15 + src/Ryujinx.Graphics.Gpu/Image/Sampler.cs | 115 + .../Image/SamplerDescriptor.cs | 260 + src/Ryujinx.Graphics.Gpu/Image/SamplerMinFilter.cs | 11 + src/Ryujinx.Graphics.Gpu/Image/SamplerMipFilter.cs | 12 + src/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs | 162 + src/Ryujinx.Graphics.Gpu/Image/SamplerPoolCache.cs | 30 + src/Ryujinx.Graphics.Gpu/Image/Texture.cs | 1705 +++++++ .../Image/TextureBindingInfo.cs | 73 + .../Image/TextureBindingsManager.cs | 882 ++++ src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs | 1180 +++++ .../Image/TextureCompatibility.cs | 911 ++++ src/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs | 43 + .../Image/TextureDependency.cs | 37 + .../Image/TextureDescriptor.cs | 273 + .../Image/TextureDescriptorType.cs | 16 + src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs | 1611 ++++++ .../Image/TextureGroupHandle.cs | 554 ++ src/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs | 381 ++ src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs | 498 ++ .../Image/TextureMatchQuality.cs | 9 + src/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs | 68 + src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs | 603 +++ src/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs | 30 + src/Ryujinx.Graphics.Gpu/Image/TextureScaleMode.cs | 16 + .../Image/TextureSearchFlags.cs | 17 + src/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs | 81 + .../Image/TextureViewCompatibility.cs | 14 + src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs | 544 ++ src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs | 38 + src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs | 507 ++ .../Memory/BufferCacheEntry.cs | 43 + src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs | 754 +++ src/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs | 125 + .../Memory/BufferModifiedRangeList.cs | 514 ++ .../Memory/BufferTextureBinding.cs | 75 + src/Ryujinx.Graphics.Gpu/Memory/CounterCache.cs | 191 + src/Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs | 102 + src/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs | 15 + src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs | 713 +++ .../Memory/MultiRangeWritableBlock.cs | 58 + src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs | 413 ++ src/Ryujinx.Graphics.Gpu/Memory/PteKind.cs | 268 + src/Ryujinx.Graphics.Gpu/Memory/ResourceKind.cs | 13 + src/Ryujinx.Graphics.Gpu/Memory/UnmapEventArgs.cs | 14 + src/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs | 13 + .../Ryujinx.Graphics.Gpu.csproj | 17 + .../Shader/CachedShaderBindings.cs | 103 + .../Shader/CachedShaderProgram.cs | 56 + .../Shader/CachedShaderStage.cs | 38 + .../Shader/ComputeShaderCacheHashTable.cs | 70 + .../Shader/DiskCache/BackgroundDiskCacheWriter.cs | 138 + .../Shader/DiskCache/BinarySerializer.cs | 216 + .../Shader/DiskCache/CompressionAlgorithm.cs | 18 + .../Shader/DiskCache/DiskCacheCommon.cs | 57 + .../Shader/DiskCache/DiskCacheGpuAccessor.cs | 266 + .../Shader/DiskCache/DiskCacheGuestStorage.cs | 459 ++ .../Shader/DiskCache/DiskCacheHostStorage.cs | 839 +++ .../Shader/DiskCache/DiskCacheLoadException.cs | 48 + .../Shader/DiskCache/DiskCacheLoadResult.cs | 72 + .../Shader/DiskCache/DiskCacheOutputStreams.cs | 57 + .../Shader/DiskCache/GuestCodeAndCbData.cs | 29 + .../Shader/DiskCache/ParallelDiskCacheLoader.cs | 725 +++ .../Shader/DiskCache/ShaderBinarySerializer.cs | 66 + src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs | 297 ++ src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs | 238 + .../Shader/GpuAccessorState.cs | 61 + .../Shader/GpuChannelComputeState.cs | 65 + .../Shader/GpuChannelGraphicsState.cs | 158 + .../Shader/GpuChannelPoolState.cs | 50 + .../Shader/HashTable/HashState.cs | 113 + .../Shader/HashTable/IDataAccessor.cs | 27 + .../Shader/HashTable/PartitionHashTable.cs | 451 ++ .../Shader/HashTable/PartitionedHashTable.cs | 244 + .../Shader/HashTable/SmartDataAccessor.cs | 96 + src/Ryujinx.Graphics.Gpu/Shader/ResourceCounts.cs | 36 + src/Ryujinx.Graphics.Gpu/Shader/ShaderAddresses.cs | 64 + src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs | 774 +++ .../Shader/ShaderCacheHashTable.cs | 282 + .../Shader/ShaderCacheState.cs | 13 + .../Shader/ShaderCodeAccessor.cs | 32 + src/Ryujinx.Graphics.Gpu/Shader/ShaderDumpPaths.cs | 49 + src/Ryujinx.Graphics.Gpu/Shader/ShaderDumper.cs | 129 + .../Shader/ShaderSpecializationList.cs | 84 + .../Shader/ShaderSpecializationState.cs | 874 ++++ .../Shader/TransformFeedbackDescriptor.cs | 58 + .../Synchronization/SynchronizationManager.cs | 143 + .../Synchronization/Syncpoint.cs | 128 + .../Synchronization/SyncpointWaiterHandle.cs | 10 + src/Ryujinx.Graphics.Gpu/Window.cs | 263 + src/Ryujinx.Graphics.Host1x/ClassId.cs | 20 + src/Ryujinx.Graphics.Host1x/Devices.cs | 32 + src/Ryujinx.Graphics.Host1x/Host1xClass.cs | 33 + .../Host1xClassRegisters.cs | 43 + src/Ryujinx.Graphics.Host1x/Host1xDevice.cs | 174 + src/Ryujinx.Graphics.Host1x/OpCode.cs | 21 + .../Ryujinx.Graphics.Host1x.csproj | 12 + src/Ryujinx.Graphics.Host1x/SyncptIncrManager.cs | 99 + src/Ryujinx.Graphics.Host1x/ThiDevice.cs | 137 + src/Ryujinx.Graphics.Host1x/ThiRegisters.cs | 24 + src/Ryujinx.Graphics.Nvdec.FFmpeg/FFmpegContext.cs | 175 + src/Ryujinx.Graphics.Nvdec.FFmpeg/H264/Decoder.cs | 56 + .../H264/H264BitStreamWriter.cs | 121 + .../H264/SpsAndPpsReconstruction.cs | 159 + .../Native/AVCodec.cs | 26 + .../Native/AVCodec501.cs | 25 + .../Native/AVCodecContext.cs | 171 + .../Native/AVCodecID.cs | 8 + .../Native/AVFrame.cs | 37 + src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVLog.cs | 15 + .../Native/AVPacket.cs | 26 + .../Native/AVRational.cs | 8 + .../Native/FFCodec.cs | 21 + .../Native/FFCodecLegacy.cs | 23 + .../Native/FFmpegApi.cs | 129 + .../Ryujinx.Graphics.Nvdec.FFmpeg.csproj | 13 + src/Ryujinx.Graphics.Nvdec.FFmpeg/Surface.cs | 41 + src/Ryujinx.Graphics.Nvdec.FFmpeg/Vp8/Decoder.cs | 53 + src/Ryujinx.Graphics.Nvdec.Vp9/BitDepth.cs | 9 + src/Ryujinx.Graphics.Nvdec.Vp9/CodecErr.cs | 56 + src/Ryujinx.Graphics.Nvdec.Vp9/Common/BitUtils.cs | 58 + .../Common/MemoryAllocator.cs | 94 + .../Common/MemoryUtil.cs | 23 + src/Ryujinx.Graphics.Nvdec.Vp9/Constants.cs | 69 + src/Ryujinx.Graphics.Nvdec.Vp9/DecodeFrame.cs | 1357 +++++ src/Ryujinx.Graphics.Nvdec.Vp9/DecodeMv.cs | 1160 +++++ src/Ryujinx.Graphics.Nvdec.Vp9/Decoder.cs | 181 + src/Ryujinx.Graphics.Nvdec.Vp9/Detokenize.cs | 325 ++ src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Convolve.cs | 943 ++++ src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Filter.cs | 12 + src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/IntraPred.cs | 1379 +++++ src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/InvTxfm.cs | 2917 +++++++++++ src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Prob.cs | 73 + src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Reader.cs | 237 + src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/TxfmCommon.cs | 54 + src/Ryujinx.Graphics.Nvdec.Vp9/Idct.cs | 536 ++ .../InternalErrorException.cs | 15 + .../InternalErrorInfo.cs | 14 + src/Ryujinx.Graphics.Nvdec.Vp9/LoopFilter.cs | 418 ++ src/Ryujinx.Graphics.Nvdec.Vp9/Luts.cs | 1610 ++++++ src/Ryujinx.Graphics.Nvdec.Vp9/PredCommon.cs | 389 ++ src/Ryujinx.Graphics.Nvdec.Vp9/QuantCommon.cs | 203 + src/Ryujinx.Graphics.Nvdec.Vp9/ReconInter.cs | 234 + src/Ryujinx.Graphics.Nvdec.Vp9/ReconIntra.cs | 762 +++ .../Ryujinx.Graphics.Nvdec.Vp9.csproj | 13 + src/Ryujinx.Graphics.Nvdec.Vp9/TileBuffer.cs | 11 + src/Ryujinx.Graphics.Nvdec.Vp9/TileWorkerData.cs | 20 + src/Ryujinx.Graphics.Nvdec.Vp9/Types/BModeInfo.cs | 10 + src/Ryujinx.Graphics.Nvdec.Vp9/Types/BlockSize.cs | 21 + src/Ryujinx.Graphics.Nvdec.Vp9/Types/Buf2D.cs | 10 + src/Ryujinx.Graphics.Nvdec.Vp9/Types/FrameType.cs | 8 + src/Ryujinx.Graphics.Nvdec.Vp9/Types/LoopFilter.cs | 27 + .../Types/LoopFilterInfoN.cs | 10 + .../Types/LoopFilterMask.cs | 24 + .../Types/LoopFilterThresh.cs | 15 + .../Types/MacroBlockD.cs | 179 + .../Types/MacroBlockDPlane.cs | 21 + src/Ryujinx.Graphics.Nvdec.Vp9/Types/ModeInfo.cs | 66 + .../Types/MotionVectorContext.cs | 14 + src/Ryujinx.Graphics.Nvdec.Vp9/Types/Mv.cs | 189 + src/Ryujinx.Graphics.Nvdec.Vp9/Types/Mv32.cs | 8 + .../Types/MvClassType.cs | 17 + .../Types/MvJointType.cs | 10 + src/Ryujinx.Graphics.Nvdec.Vp9/Types/MvRef.cs | 10 + .../Types/PartitionType.cs | 12 + src/Ryujinx.Graphics.Nvdec.Vp9/Types/PlaneType.cs | 9 + src/Ryujinx.Graphics.Nvdec.Vp9/Types/Position.cs | 14 + .../Types/PredictionMode.cs | 21 + src/Ryujinx.Graphics.Nvdec.Vp9/Types/RefBuffer.cs | 8 + .../Types/ReferenceMode.cs | 10 + .../Types/ScaleFactors.cs | 451 ++ .../Types/SegLvlFeatures.cs | 11 + .../Types/Segmentation.cs | 71 + src/Ryujinx.Graphics.Nvdec.Vp9/Types/Surface.cs | 82 + src/Ryujinx.Graphics.Nvdec.Vp9/Types/TileInfo.cs | 85 + src/Ryujinx.Graphics.Nvdec.Vp9/Types/TxMode.cs | 12 + src/Ryujinx.Graphics.Nvdec.Vp9/Types/TxSize.cs | 11 + src/Ryujinx.Graphics.Nvdec.Vp9/Types/TxType.cs | 11 + src/Ryujinx.Graphics.Nvdec.Vp9/Types/Vp9Common.cs | 331 ++ src/Ryujinx.Graphics.Nvdec/ApplicationId.cs | 14 + src/Ryujinx.Graphics.Nvdec/H264Decoder.cs | 57 + src/Ryujinx.Graphics.Nvdec/Image/SurfaceCache.cs | 174 + src/Ryujinx.Graphics.Nvdec/Image/SurfaceCommon.cs | 26 + src/Ryujinx.Graphics.Nvdec/Image/SurfaceReader.cs | 133 + src/Ryujinx.Graphics.Nvdec/Image/SurfaceWriter.cs | 175 + src/Ryujinx.Graphics.Nvdec/MemoryExtensions.cs | 28 + src/Ryujinx.Graphics.Nvdec/NvdecDecoderContext.cs | 29 + src/Ryujinx.Graphics.Nvdec/NvdecDevice.cs | 83 + src/Ryujinx.Graphics.Nvdec/NvdecRegisters.cs | 63 + src/Ryujinx.Graphics.Nvdec/NvdecStatus.cs | 16 + src/Ryujinx.Graphics.Nvdec/ResourceManager.cs | 17 + .../Ryujinx.Graphics.Nvdec.csproj | 18 + .../Types/H264/PictureInfo.cs | 122 + .../Types/H264/ReferenceFrame.cs | 15 + .../Types/Vp8/PictureInfo.cs | 75 + .../Types/Vp9/BackwardUpdates.cs | 72 + .../Types/Vp9/EntropyProbs.cs | 141 + src/Ryujinx.Graphics.Nvdec/Types/Vp9/FrameFlags.cs | 12 + src/Ryujinx.Graphics.Nvdec/Types/Vp9/FrameSize.cs | 12 + src/Ryujinx.Graphics.Nvdec/Types/Vp9/FrameStats.cs | 21 + src/Ryujinx.Graphics.Nvdec/Types/Vp9/LoopFilter.cs | 13 + .../Types/Vp9/PictureInfo.cs | 87 + .../Types/Vp9/Segmentation.cs | 16 + src/Ryujinx.Graphics.Nvdec/Vp8Decoder.cs | 33 + src/Ryujinx.Graphics.Nvdec/Vp9Decoder.cs | 90 + .../BackgroundContextWorker.cs | 91 + src/Ryujinx.Graphics.OpenGL/Buffer.cs | 103 + src/Ryujinx.Graphics.OpenGL/Constants.cs | 11 + src/Ryujinx.Graphics.OpenGL/Debugger.cs | 101 + .../DrawTextureEmulation.cs | 138 + .../Effects/FsrScalingFilter.cs | 177 + .../Effects/FxaaPostProcessingEffect.cs | 81 + .../Effects/IPostProcessingEffect.cs | 11 + .../Effects/IScalingFilter.cs | 18 + .../Effects/ShaderHelper.cs | 40 + .../Effects/Shaders/ffx_a.h | 2656 ++++++++++ .../Effects/Shaders/ffx_fsr1.h | 1199 +++++ .../Effects/Shaders/fsr_scaling.glsl | 88 + .../Effects/Shaders/fsr_sharpening.glsl | 37 + .../Effects/Shaders/fxaa.glsl | 1174 +++++ .../Effects/Shaders/smaa.hlsl | 1361 +++++ .../Effects/Shaders/smaa_blend.glsl | 26 + .../Effects/Shaders/smaa_edge.glsl | 24 + .../Effects/Shaders/smaa_neighbour.glsl | 26 + .../Effects/SmaaPostProcessingEffect.cs | 261 + .../Effects/Textures/SmaaAreaTexture.bin | Bin 0 -> 179200 bytes .../Effects/Textures/SmaaSearchTexture.bin | Bin 0 -> 1024 bytes src/Ryujinx.Graphics.OpenGL/EnumConversion.cs | 675 +++ src/Ryujinx.Graphics.OpenGL/FormatInfo.cs | 45 + src/Ryujinx.Graphics.OpenGL/FormatTable.cs | 225 + src/Ryujinx.Graphics.OpenGL/Framebuffer.cs | 247 + src/Ryujinx.Graphics.OpenGL/Handle.cs | 23 + src/Ryujinx.Graphics.OpenGL/Helper/GLXHelper.cs | 36 + src/Ryujinx.Graphics.OpenGL/Helper/WGLHelper.cs | 15 + src/Ryujinx.Graphics.OpenGL/HwCapabilities.cs | 142 + src/Ryujinx.Graphics.OpenGL/IOpenGLContext.cs | 27 + .../Image/FormatConverter.cs | 149 + src/Ryujinx.Graphics.OpenGL/Image/ITextureInfo.cs | 14 + .../Image/IntermmediatePool.cs | 103 + src/Ryujinx.Graphics.OpenGL/Image/Sampler.cs | 64 + src/Ryujinx.Graphics.OpenGL/Image/TextureBase.cs | 44 + src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs | 108 + src/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs | 524 ++ .../Image/TextureCopyIncompatible.cs | 252 + src/Ryujinx.Graphics.OpenGL/Image/TextureCopyMS.cs | 276 + .../Image/TextureStorage.cs | 212 + src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs | 867 ++++ src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs | 276 + src/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs | 136 + src/Ryujinx.Graphics.OpenGL/Pipeline.cs | 1770 +++++++ src/Ryujinx.Graphics.OpenGL/Program.cs | 177 + .../Queries/BufferedQuery.cs | 120 + .../Queries/CounterQueue.cs | 229 + .../Queries/CounterQueueEvent.cs | 163 + src/Ryujinx.Graphics.OpenGL/Queries/Counters.cs | 57 + src/Ryujinx.Graphics.OpenGL/ResourcePool.cs | 122 + .../Ryujinx.Graphics.OpenGL.csproj | 32 + src/Ryujinx.Graphics.OpenGL/Sync.cs | 168 + src/Ryujinx.Graphics.OpenGL/VertexArray.cs | 280 + src/Ryujinx.Graphics.OpenGL/Window.cs | 420 ++ src/Ryujinx.Graphics.Shader/AlphaTestOp.cs | 14 + src/Ryujinx.Graphics.Shader/AttributeType.cs | 38 + src/Ryujinx.Graphics.Shader/BufferDescriptor.cs | 26 + src/Ryujinx.Graphics.Shader/BufferUsageFlags.cs | 18 + .../CodeGen/Glsl/CodeGenContext.cs | 95 + .../CodeGen/Glsl/Declarations.cs | 818 +++ .../CodeGen/Glsl/DefaultNames.cs | 37 + .../CodeGen/Glsl/GlslGenerator.cs | 154 + .../HelperFunctions/AtomicMinMaxS32Shared.glsl | 21 + .../HelperFunctions/AtomicMinMaxS32Storage.glsl | 21 + .../Glsl/HelperFunctions/HelperFunctionNames.cs | 22 + .../Glsl/HelperFunctions/MultiplyHighS32.glsl | 7 + .../Glsl/HelperFunctions/MultiplyHighU32.glsl | 7 + .../CodeGen/Glsl/HelperFunctions/Shuffle.glsl | 11 + .../CodeGen/Glsl/HelperFunctions/ShuffleDown.glsl | 11 + .../CodeGen/Glsl/HelperFunctions/ShuffleUp.glsl | 9 + .../CodeGen/Glsl/HelperFunctions/ShuffleXor.glsl | 11 + .../Glsl/HelperFunctions/StoreSharedSmallInt.glsl | 23 + .../Glsl/HelperFunctions/StoreStorageSmallInt.glsl | 23 + .../CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl | 7 + .../Glsl/HelperFunctions/TexelFetchScale_cp.glsl | 19 + .../Glsl/HelperFunctions/TexelFetchScale_fp.glsl | 26 + .../Glsl/HelperFunctions/TexelFetchScale_vp.glsl | 20 + .../CodeGen/Glsl/Instructions/InstGen.cs | 238 + .../CodeGen/Glsl/Instructions/InstGenBallot.cs | 27 + .../CodeGen/Glsl/Instructions/InstGenCall.cs | 29 + .../CodeGen/Glsl/Instructions/InstGenFSI.cs | 29 + .../CodeGen/Glsl/Instructions/InstGenHelper.cs | 231 + .../CodeGen/Glsl/Instructions/InstGenMemory.cs | 939 ++++ .../CodeGen/Glsl/Instructions/InstGenPacking.cs | 56 + .../CodeGen/Glsl/Instructions/InstGenVector.cs | 32 + .../CodeGen/Glsl/Instructions/InstInfo.cs | 18 + .../CodeGen/Glsl/Instructions/InstType.cs | 33 + .../CodeGen/Glsl/Instructions/IoMap.cs | 145 + .../CodeGen/Glsl/NumberFormatter.cs | 104 + .../CodeGen/Glsl/OperandManager.cs | 254 + .../CodeGen/Glsl/TypeConversion.cs | 87 + .../CodeGen/Spirv/CodeGenContext.cs | 409 ++ .../CodeGen/Spirv/Declarations.cs | 615 +++ .../CodeGen/Spirv/EnumConversion.cs | 22 + .../CodeGen/Spirv/Instructions.cs | 2480 +++++++++ src/Ryujinx.Graphics.Shader/CodeGen/Spirv/IoMap.cs | 86 + .../CodeGen/Spirv/OperationResult.cs | 19 + .../CodeGen/Spirv/ScalingHelpers.cs | 227 + .../CodeGen/Spirv/SpirvDelegates.cs | 226 + .../CodeGen/Spirv/SpirvGenerator.cs | 415 ++ .../CodeGen/Spirv/TextureMeta.cs | 4 + src/Ryujinx.Graphics.Shader/Constants.cs | 16 + src/Ryujinx.Graphics.Shader/Decoders/Block.cs | 168 + .../Decoders/DecodedFunction.cs | 48 + .../Decoders/DecodedProgram.cs | 57 + src/Ryujinx.Graphics.Shader/Decoders/Decoder.cs | 765 +++ .../Decoders/FunctionType.cs | 10 + .../Decoders/InstDecoders.cs | 5383 ++++++++++++++++++++ src/Ryujinx.Graphics.Shader/Decoders/InstName.cs | 188 + src/Ryujinx.Graphics.Shader/Decoders/InstOp.cs | 27 + src/Ryujinx.Graphics.Shader/Decoders/InstProps.cs | 28 + src/Ryujinx.Graphics.Shader/Decoders/InstTable.cs | 390 ++ src/Ryujinx.Graphics.Shader/Decoders/Register.cs | 36 + .../Decoders/RegisterConsts.cs | 13 + .../Decoders/RegisterType.cs | 9 + src/Ryujinx.Graphics.Shader/IGpuAccessor.cs | 528 ++ src/Ryujinx.Graphics.Shader/InputTopology.cs | 40 + .../Instructions/AttributeMap.cs | 351 ++ .../Instructions/InstEmit.cs | 379 ++ .../Instructions/InstEmitAluHelper.cs | 160 + .../Instructions/InstEmitAttribute.cs | 383 ++ .../Instructions/InstEmitBarrier.cs | 44 + .../Instructions/InstEmitBitfield.cs | 194 + .../Instructions/InstEmitConditionCode.cs | 87 + .../Instructions/InstEmitConversion.cs | 425 ++ .../Instructions/InstEmitFloatArithmetic.cs | 532 ++ .../Instructions/InstEmitFloatComparison.cs | 575 +++ .../Instructions/InstEmitFloatMinMax.cs | 106 + .../Instructions/InstEmitFlowControl.cs | 322 ++ .../Instructions/InstEmitHelper.cs | 266 + .../Instructions/InstEmitIntegerArithmetic.cs | 699 +++ .../Instructions/InstEmitIntegerComparison.cs | 310 ++ .../Instructions/InstEmitIntegerLogical.cs | 167 + .../Instructions/InstEmitIntegerMinMax.cs | 71 + .../Instructions/InstEmitMemory.cs | 541 ++ .../Instructions/InstEmitMove.cs | 237 + .../Instructions/InstEmitMultifunction.cs | 97 + .../Instructions/InstEmitNop.cs | 15 + .../Instructions/InstEmitPredicate.cs | 54 + .../Instructions/InstEmitShift.cs | 249 + .../Instructions/InstEmitSurface.cs | 796 +++ .../Instructions/InstEmitTexture.cs | 1312 +++++ .../Instructions/InstEmitVideoArithmetic.cs | 118 + .../Instructions/InstEmitVideoMinMax.cs | 183 + .../Instructions/InstEmitWarp.cs | 84 + .../Instructions/InstEmitter.cs | 6 + .../Instructions/Lop3Expression.cs | 141 + .../IntermediateRepresentation/BasicBlock.cs | 91 + .../IntermediateRepresentation/CommentNode.cs | 12 + .../IntermediateRepresentation/Function.cs | 23 + .../IntermediateRepresentation/INode.cs | 15 + .../IntermediateRepresentation/Instruction.cs | 178 + .../IntermediateRepresentation/IoVariable.cs | 51 + .../IntermediateRepresentation/IrConsts.cs | 8 + .../IntermediateRepresentation/Operand.cs | 79 + .../IntermediateRepresentation/OperandHelper.cs | 62 + .../IntermediateRepresentation/OperandType.cs | 13 + .../IntermediateRepresentation/Operation.cs | 257 + .../IntermediateRepresentation/PhiNode.cs | 107 + .../IntermediateRepresentation/StorageKind.cs | 39 + .../IntermediateRepresentation/TextureFlags.cs | 32 + .../IntermediateRepresentation/TextureOperation.cs | 69 + src/Ryujinx.Graphics.Shader/OutputTopology.cs | 24 + .../Ryujinx.Graphics.Shader.csproj | 33 + src/Ryujinx.Graphics.Shader/SamplerType.cs | 100 + .../ShaderIdentification.cs | 8 + src/Ryujinx.Graphics.Shader/ShaderProgram.cs | 35 + src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs | 51 + src/Ryujinx.Graphics.Shader/ShaderStage.cs | 27 + .../StructuredIr/AstAssignment.cs | 35 + .../StructuredIr/AstBlock.cs | 117 + .../StructuredIr/AstBlockType.cs | 12 + .../StructuredIr/AstBlockVisitor.cs | 68 + .../StructuredIr/AstComment.cs | 12 + .../StructuredIr/AstHelper.cs | 74 + .../StructuredIr/AstNode.cs | 11 + .../StructuredIr/AstOperand.cs | 50 + .../StructuredIr/AstOperation.cs | 80 + .../StructuredIr/AstOptimizer.cs | 155 + .../StructuredIr/AstTextureOperation.cs | 36 + .../StructuredIr/GotoElimination.cs | 459 ++ .../StructuredIr/GotoStatement.cs | 23 + .../StructuredIr/HelperFunctionsMask.cs | 21 + .../StructuredIr/IAstNode.cs | 11 + .../StructuredIr/InstructionInfo.cs | 216 + .../StructuredIr/IoDefinition.cs | 44 + .../StructuredIr/OperandInfo.cs | 33 + .../StructuredIr/PhiFunctions.cs | 45 + .../StructuredIr/StructuredFunction.cs | 42 + .../StructuredIr/StructuredProgram.cs | 421 ++ .../StructuredIr/StructuredProgramContext.cs | 330 ++ .../StructuredIr/StructuredProgramInfo.cs | 36 + src/Ryujinx.Graphics.Shader/SupportBuffer.cs | 58 + src/Ryujinx.Graphics.Shader/TessPatchType.cs | 22 + src/Ryujinx.Graphics.Shader/TessSpacing.cs | 22 + src/Ryujinx.Graphics.Shader/TextureDescriptor.cs | 34 + src/Ryujinx.Graphics.Shader/TextureFormat.cs | 128 + src/Ryujinx.Graphics.Shader/TextureHandle.cs | 124 + src/Ryujinx.Graphics.Shader/TextureUsageFlags.cs | 19 + .../Translation/AggregateType.cs | 25 + .../Translation/AttributeConsts.cs | 36 + .../Translation/ControlFlowGraph.cs | 176 + .../Translation/Dominance.cs | 94 + .../Translation/EmitterContext.cs | 492 ++ .../Translation/EmitterContextInsts.cs | 819 +++ .../Translation/FeatureFlags.cs | 27 + .../Translation/FunctionMatch.cs | 866 ++++ .../Translation/GlobalMemory.cs | 52 + .../Optimizations/BindlessElimination.cs | 263 + .../Translation/Optimizations/BindlessToIndexed.cs | 85 + .../Translation/Optimizations/BranchElimination.cs | 64 + .../Translation/Optimizations/ConstantFolding.cs | 346 ++ .../Translation/Optimizations/GlobalToStorage.cs | 433 ++ .../Translation/Optimizations/Optimizer.cs | 380 ++ .../Translation/Optimizations/Simplification.cs | 147 + .../Translation/Optimizations/Utils.cs | 68 + .../Translation/RegisterUsage.cs | 486 ++ .../Translation/Rewriter.cs | 768 +++ .../Translation/ShaderConfig.cs | 944 ++++ .../Translation/ShaderHeader.cs | 158 + .../Translation/ShaderIdentifier.cs | 185 + src/Ryujinx.Graphics.Shader/Translation/Ssa.cs | 376 ++ .../Translation/TargetApi.cs | 8 + .../Translation/TargetLanguage.cs | 9 + .../Translation/TranslationFlags.cs | 14 + .../Translation/TranslationOptions.cs | 16 + .../Translation/Translator.cs | 362 ++ .../Translation/TranslatorContext.cs | 255 + src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs | 1621 ++++++ .../Astc/AstcDecoderException.cs | 9 + src/Ryujinx.Graphics.Texture/Astc/AstcPixel.cs | 68 + src/Ryujinx.Graphics.Texture/Astc/BitStream128.cs | 72 + src/Ryujinx.Graphics.Texture/Astc/Bits.cs | 66 + src/Ryujinx.Graphics.Texture/Astc/EndPointSet.cs | 23 + .../Astc/IntegerEncoded.cs | 345 ++ .../Astc/IntegerSequence.cs | 31 + src/Ryujinx.Graphics.Texture/BC6Decoder.cs | 819 +++ src/Ryujinx.Graphics.Texture/BC7Decoder.cs | 220 + src/Ryujinx.Graphics.Texture/BCnDecoder.cs | 894 ++++ src/Ryujinx.Graphics.Texture/BCnEncoder.cs | 60 + .../BlockLinearConstants.cs | 10 + src/Ryujinx.Graphics.Texture/BlockLinearLayout.cs | 195 + src/Ryujinx.Graphics.Texture/Bpp12Pixel.cs | 11 + src/Ryujinx.Graphics.Texture/ETC2Decoder.cs | 682 +++ .../Encoders/BC7Encoder.cs | 1005 ++++ .../Encoders/EncodeMode.cs | 10 + src/Ryujinx.Graphics.Texture/LayoutConverter.cs | 591 +++ src/Ryujinx.Graphics.Texture/OffsetCalculator.cs | 141 + src/Ryujinx.Graphics.Texture/PixelConverter.cs | 216 + src/Ryujinx.Graphics.Texture/Region.cs | 14 + .../Ryujinx.Graphics.Texture.csproj | 11 + src/Ryujinx.Graphics.Texture/Size.cs | 16 + src/Ryujinx.Graphics.Texture/SizeCalculator.cs | 287 ++ src/Ryujinx.Graphics.Texture/SizeInfo.cs | 119 + src/Ryujinx.Graphics.Texture/Utils/BC67Tables.cs | 297 ++ src/Ryujinx.Graphics.Texture/Utils/BC67Utils.cs | 1327 +++++ src/Ryujinx.Graphics.Texture/Utils/BC7ModeInfo.cs | 37 + src/Ryujinx.Graphics.Texture/Utils/Block.cs | 55 + src/Ryujinx.Graphics.Texture/Utils/RgbaColor32.cs | 229 + src/Ryujinx.Graphics.Texture/Utils/RgbaColor8.cs | 84 + src/Ryujinx.Graphics.Vic/Blender.cs | 278 + src/Ryujinx.Graphics.Vic/Image/BufferPool.cs | 103 + src/Ryujinx.Graphics.Vic/Image/InputSurface.cs | 86 + src/Ryujinx.Graphics.Vic/Image/Pixel.cs | 10 + src/Ryujinx.Graphics.Vic/Image/Surface.cs | 46 + src/Ryujinx.Graphics.Vic/Image/SurfaceCommon.cs | 33 + src/Ryujinx.Graphics.Vic/Image/SurfaceReader.cs | 495 ++ src/Ryujinx.Graphics.Vic/Image/SurfaceWriter.cs | 667 +++ src/Ryujinx.Graphics.Vic/Rectangle.cs | 18 + src/Ryujinx.Graphics.Vic/ResourceManager.cs | 19 + .../Ryujinx.Graphics.Vic.csproj | 16 + src/Ryujinx.Graphics.Vic/Scaler.cs | 124 + .../Types/BlendingSlotStruct.cs | 29 + src/Ryujinx.Graphics.Vic/Types/ClearRectStruct.cs | 21 + src/Ryujinx.Graphics.Vic/Types/ConfigStruct.cs | 16 + src/Ryujinx.Graphics.Vic/Types/DeinterlaceMode.cs | 12 + src/Ryujinx.Graphics.Vic/Types/FrameFormat.cs | 79 + src/Ryujinx.Graphics.Vic/Types/LumaKeyStruct.cs | 19 + src/Ryujinx.Graphics.Vic/Types/MatrixStruct.cs | 27 + src/Ryujinx.Graphics.Vic/Types/OutputConfig.cs | 27 + .../Types/OutputSurfaceConfig.cs | 24 + src/Ryujinx.Graphics.Vic/Types/PipeConfig.cs | 15 + src/Ryujinx.Graphics.Vic/Types/PixelFormat.cs | 81 + src/Ryujinx.Graphics.Vic/Types/SlotConfig.cs | 65 + src/Ryujinx.Graphics.Vic/Types/SlotStruct.cs | 12 + .../Types/SlotSurfaceConfig.cs | 23 + src/Ryujinx.Graphics.Vic/VicDevice.cs | 74 + src/Ryujinx.Graphics.Vic/VicRegisters.cs | 51 + src/Ryujinx.Graphics.Video/FrameField.cs | 8 + src/Ryujinx.Graphics.Video/H264PictureInfo.cs | 47 + src/Ryujinx.Graphics.Video/IDecoder.cs | 11 + src/Ryujinx.Graphics.Video/IH264Decoder.cs | 9 + src/Ryujinx.Graphics.Video/ISurface.cs | 20 + src/Ryujinx.Graphics.Video/IVp9Decoder.cs | 14 + src/Ryujinx.Graphics.Video/Plane.cs | 6 + .../Ryujinx.Graphics.Video.csproj | 11 + src/Ryujinx.Graphics.Video/Vp8PictureInfo.cs | 11 + src/Ryujinx.Graphics.Video/Vp9BackwardUpdates.cs | 32 + src/Ryujinx.Graphics.Video/Vp9EntropyProbs.cs | 36 + src/Ryujinx.Graphics.Video/Vp9Mv.cs | 8 + src/Ryujinx.Graphics.Video/Vp9MvRef.cs | 11 + src/Ryujinx.Graphics.Video/Vp9PictureInfo.cs | 39 + src/Ryujinx.Graphics.Vulkan/Auto.cs | 154 + src/Ryujinx.Graphics.Vulkan/AutoFlushCounter.cs | 179 + src/Ryujinx.Graphics.Vulkan/BackgroundResources.cs | 117 + src/Ryujinx.Graphics.Vulkan/BitMap.cs | 157 + .../BufferAllocationType.cs | 12 + src/Ryujinx.Graphics.Vulkan/BufferHolder.cs | 788 +++ src/Ryujinx.Graphics.Vulkan/BufferManager.cs | 455 ++ src/Ryujinx.Graphics.Vulkan/BufferState.cs | 48 + src/Ryujinx.Graphics.Vulkan/BufferUsageBitmap.cs | 77 + src/Ryujinx.Graphics.Vulkan/CacheByRange.cs | 398 ++ src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs | 368 ++ src/Ryujinx.Graphics.Vulkan/CommandBufferScoped.cs | 44 + src/Ryujinx.Graphics.Vulkan/Constants.cs | 20 + .../DescriptorSetCollection.cs | 246 + .../DescriptorSetManager.cs | 201 + .../DescriptorSetUpdater.cs | 674 +++ src/Ryujinx.Graphics.Vulkan/DisposableBuffer.cs | 25 + .../DisposableBufferView.cs | 25 + .../DisposableFramebuffer.cs | 25 + src/Ryujinx.Graphics.Vulkan/DisposableImage.cs | 25 + src/Ryujinx.Graphics.Vulkan/DisposableImageView.cs | 25 + src/Ryujinx.Graphics.Vulkan/DisposableMemory.cs | 24 + src/Ryujinx.Graphics.Vulkan/DisposablePipeline.cs | 25 + .../DisposableRenderPass.cs | 25 + src/Ryujinx.Graphics.Vulkan/DisposableSampler.cs | 25 + .../Effects/FsrScalingFilter.cs | 179 + .../Effects/FxaaPostProcessingEffect.cs | 111 + .../Effects/IPostProcessingEffect.cs | 10 + .../Effects/IScalingFilter.cs | 20 + .../Effects/Shaders/FsrScaling.glsl | 3945 ++++++++++++++ .../Effects/Shaders/FsrScaling.spv | Bin 0 -> 44672 bytes .../Effects/Shaders/FsrSharpening.glsl | 3904 ++++++++++++++ .../Effects/Shaders/FsrSharpening.spv | Bin 0 -> 20472 bytes .../Effects/Shaders/Fxaa.glsl | 1177 +++++ .../Effects/Shaders/Fxaa.spv | Bin 0 -> 25012 bytes .../Effects/Shaders/SmaaBlend.glsl | 1404 +++++ .../Effects/Shaders/SmaaBlend.spv | Bin 0 -> 33728 bytes .../Effects/Shaders/SmaaEdge.glsl | 1402 +++++ .../Effects/Shaders/SmaaEdge.spv | Bin 0 -> 8464 bytes .../Effects/Shaders/SmaaNeighbour.glsl | 1403 +++++ .../Effects/Shaders/SmaaNeighbour.spv | Bin 0 -> 8328 bytes .../Effects/SmaaConstants.cs | 15 + .../Effects/SmaaPostProcessingEffect.cs | 289 ++ .../Effects/Textures/SmaaAreaTexture.bin | Bin 0 -> 179200 bytes .../Effects/Textures/SmaaSearchTexture.bin | Bin 0 -> 1024 bytes src/Ryujinx.Graphics.Vulkan/EnumConversion.cs | 374 ++ src/Ryujinx.Graphics.Vulkan/FenceHelper.cs | 30 + src/Ryujinx.Graphics.Vulkan/FenceHolder.cs | 79 + src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs | 164 + src/Ryujinx.Graphics.Vulkan/FormatConverter.cs | 49 + src/Ryujinx.Graphics.Vulkan/FormatTable.cs | 172 + src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs | 240 + .../HardwareCapabilities.cs | 120 + src/Ryujinx.Graphics.Vulkan/HashTableSlim.cs | 112 + src/Ryujinx.Graphics.Vulkan/HelperShader.cs | 1683 ++++++ src/Ryujinx.Graphics.Vulkan/IdList.cs | 123 + src/Ryujinx.Graphics.Vulkan/IndexBufferPattern.cs | 139 + src/Ryujinx.Graphics.Vulkan/IndexBufferState.cs | 161 + src/Ryujinx.Graphics.Vulkan/MemoryAllocation.cs | 37 + src/Ryujinx.Graphics.Vulkan/MemoryAllocator.cs | 101 + .../MemoryAllocatorBlockList.cs | 282 + .../MoltenVK/MVKConfiguration.cs | 104 + .../MoltenVK/MVKInitialization.cs | 31 + src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs | 212 + src/Ryujinx.Graphics.Vulkan/NativeArray.cs | 48 + .../PersistentFlushBuffer.cs | 89 + src/Ryujinx.Graphics.Vulkan/PipelineBase.cs | 1742 +++++++ src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs | 318 ++ .../PipelineDynamicState.cs | 170 + src/Ryujinx.Graphics.Vulkan/PipelineFull.cs | 314 ++ .../PipelineHelperShader.cs | 59 + src/Ryujinx.Graphics.Vulkan/PipelineLayoutCache.cs | 58 + .../PipelineLayoutCacheEntry.cs | 112 + .../PipelineLayoutFactory.cs | 244 + src/Ryujinx.Graphics.Vulkan/PipelineState.cs | 621 +++ src/Ryujinx.Graphics.Vulkan/PipelineUid.cs | 129 + .../Queries/BufferedQuery.cs | 216 + .../Queries/CounterQueue.cs | 245 + .../Queries/CounterQueueEvent.cs | 170 + src/Ryujinx.Graphics.Vulkan/Queries/Counters.cs | 71 + .../Ryujinx.Graphics.Vulkan.csproj | 39 + src/Ryujinx.Graphics.Vulkan/SamplerHolder.cs | 118 + src/Ryujinx.Graphics.Vulkan/SemaphoreHolder.cs | 60 + src/Ryujinx.Graphics.Vulkan/Shader.cs | 163 + src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs | 427 ++ .../Shaders/ChangeBufferStrideShaderSource.comp | 64 + .../ColorBlitClearAlphaFragmentShaderSource.frag | 11 + .../Shaders/ColorBlitFragmentShaderSource.frag | 11 + .../Shaders/ColorBlitMsFragmentShaderSource.frag | 11 + .../Shaders/ColorBlitVertexShaderSource.vert | 20 + .../Shaders/ColorClearFFragmentShaderSource.frag | 9 + .../Shaders/ColorClearSIFragmentShaderSource.frag | 9 + .../Shaders/ColorClearUIFragmentShaderSource.frag | 9 + .../Shaders/ColorClearVertexShaderSource.vert | 19 + .../ColorCopyShorteningComputeShaderSource.comp | 36 + .../ColorCopyToNonMsComputeShaderSource.comp | 37 + .../ColorCopyWideningComputeShaderSource.comp | 31 + .../Shaders/ColorDrawToMsFragmentShaderSource.frag | 27 + .../Shaders/ColorDrawToMsVertexShaderSource.vert | 11 + .../Shaders/ConvertIndexBufferShaderSource.comp | 58 + .../Shaders/ConvertIndirectDataShaderSource.comp | 103 + .../Shaders/DepthBlitFragmentShaderSource.frag | 10 + .../Shaders/DepthBlitMsFragmentShaderSource.frag | 10 + .../Shaders/DepthDrawToMsFragmentShaderSource.frag | 25 + .../DepthDrawToNonMsFragmentShaderSource.frag | 28 + .../Shaders/ShaderBinaries.cs | 2413 +++++++++ .../Shaders/StencilBlitFragmentShaderSource.frag | 12 + .../Shaders/StencilBlitMsFragmentShaderSource.frag | 12 + .../StencilDrawToMsFragmentShaderSource.frag | 27 + .../StencilDrawToNonMsFragmentShaderSource.frag | 30 + src/Ryujinx.Graphics.Vulkan/SpecInfo.cs | 102 + src/Ryujinx.Graphics.Vulkan/StagingBuffer.cs | 194 + src/Ryujinx.Graphics.Vulkan/SyncManager.cs | 206 + src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs | 160 + src/Ryujinx.Graphics.Vulkan/TextureCopy.cs | 476 ++ src/Ryujinx.Graphics.Vulkan/TextureStorage.cs | 530 ++ src/Ryujinx.Graphics.Vulkan/TextureView.cs | 885 ++++ src/Ryujinx.Graphics.Vulkan/Vendor.cs | 62 + src/Ryujinx.Graphics.Vulkan/VertexBufferState.cs | 153 + src/Ryujinx.Graphics.Vulkan/VulkanConfiguration.cs | 12 + .../VulkanDebugMessenger.cs | 153 + src/Ryujinx.Graphics.Vulkan/VulkanException.cs | 41 + .../VulkanInitialization.cs | 539 ++ src/Ryujinx.Graphics.Vulkan/VulkanInstance.cs | 127 + .../VulkanPhysicalDevice.cs | 70 + src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 820 +++ src/Ryujinx.Graphics.Vulkan/Window.cs | 603 +++ src/Ryujinx.Graphics.Vulkan/WindowBase.cs | 18 + src/Ryujinx.HLE/AssemblyInfo.cs | 3 + .../Exceptions/GuestBrokeExecutionException.cs | 11 + .../Exceptions/InternalServiceException.cs | 9 + .../Exceptions/InvalidFirmwarePackageException.cs | 9 + src/Ryujinx.HLE/Exceptions/InvalidNpdmException.cs | 9 + .../Exceptions/InvalidStructLayoutException.cs | 15 + .../Exceptions/InvalidSystemResourceException.cs | 9 + .../Exceptions/ServiceNotImplementedException.cs | 164 + .../Exceptions/TamperCompilationException.cs | 9 + .../Exceptions/TamperExecutionException.cs | 9 + .../Exceptions/UndefinedInstructionException.cs | 13 + src/Ryujinx.HLE/FileSystem/ContentManager.cs | 1048 ++++ src/Ryujinx.HLE/FileSystem/ContentPath.cs | 82 + .../FileSystem/EncryptedFileSystemCreator.cs | 26 + src/Ryujinx.HLE/FileSystem/LocationEntry.cs | 25 + src/Ryujinx.HLE/FileSystem/SystemVersion.cs | 40 + src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs | 615 +++ src/Ryujinx.HLE/HLEConfiguration.cs | 219 + src/Ryujinx.HLE/HOS/Applets/AppletManager.cs | 37 + .../HOS/Applets/Browser/BootDisplayKind.cs | 11 + .../HOS/Applets/Browser/BrowserApplet.cs | 104 + .../HOS/Applets/Browser/BrowserArgument.cs | 133 + .../HOS/Applets/Browser/BrowserOutput.cs | 47 + .../HOS/Applets/Browser/BrowserOutputType.cs | 14 + .../HOS/Applets/Browser/DocumentKind.cs | 9 + .../HOS/Applets/Browser/LeftStickMode.cs | 8 + src/Ryujinx.HLE/HOS/Applets/Browser/ShimKind.cs | 13 + .../HOS/Applets/Browser/WebArgHeader.cs | 9 + src/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLV.cs | 9 + .../HOS/Applets/Browser/WebArgTLVType.cs | 62 + .../HOS/Applets/Browser/WebCommonReturnValue.cs | 12 + .../HOS/Applets/Browser/WebExitReason.cs | 11 + src/Ryujinx.HLE/HOS/Applets/CommonArguments.cs | 16 + .../HOS/Applets/Controller/ControllerApplet.cs | 147 + .../Applets/Controller/ControllerAppletUiArgs.cs | 14 + .../Controller/ControllerSupportArgHeader.cs | 18 + .../Controller/ControllerSupportArgPrivate.cs | 16 + .../Applets/Controller/ControllerSupportArgV7.cs | 26 + .../Controller/ControllerSupportArgVPre7.cs | 26 + .../Applets/Controller/ControllerSupportMode.cs | 9 + .../Controller/ControllerSupportResultInfo.cs | 16 + .../HOS/Applets/Error/ApplicationErrorArg.cs | 14 + src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs | 216 + .../HOS/Applets/Error/ErrorCommonArg.cs | 12 + .../HOS/Applets/Error/ErrorCommonHeader.cs | 17 + src/Ryujinx.HLE/HOS/Applets/Error/ErrorType.cs | 13 + src/Ryujinx.HLE/HOS/Applets/IApplet.cs | 28 + .../HOS/Applets/PlayerSelect/PlayerSelectApplet.cs | 58 + .../HOS/Applets/PlayerSelect/PlayerSelectResult.cs | 8 + .../SoftwareKeyboard/InitialCursorPosition.cs | 18 + .../SoftwareKeyboard/InlineKeyboardRequest.cs | 48 + .../SoftwareKeyboard/InlineKeyboardResponse.cs | 93 + .../SoftwareKeyboard/InlineKeyboardState.cs | 33 + .../Applets/SoftwareKeyboard/InlineResponses.cs | 298 ++ .../HOS/Applets/SoftwareKeyboard/InputFormMode.cs | 18 + .../Applets/SoftwareKeyboard/InvalidButtonFlags.cs | 17 + .../Applets/SoftwareKeyboard/InvalidCharFlags.cs | 56 + .../Applets/SoftwareKeyboard/KeyboardCalcFlags.cs | 26 + .../Applets/SoftwareKeyboard/KeyboardInputMode.cs | 14 + .../KeyboardMiniaturizationMode.cs | 12 + .../HOS/Applets/SoftwareKeyboard/KeyboardMode.cs | 31 + .../HOS/Applets/SoftwareKeyboard/KeyboardResult.cs | 12 + .../HOS/Applets/SoftwareKeyboard/PasswordMode.cs | 18 + .../SoftwareKeyboard/Resources/Icon_BtnA.png | Bin 0 -> 1074 bytes .../SoftwareKeyboard/Resources/Icon_BtnA.svg | 80 + .../SoftwareKeyboard/Resources/Icon_BtnB.png | Bin 0 -> 992 bytes .../SoftwareKeyboard/Resources/Icon_BtnB.svg | 93 + .../SoftwareKeyboard/Resources/Icon_KeyF6.png | Bin 0 -> 842 bytes .../SoftwareKeyboard/Resources/Icon_KeyF6.svg | 108 + .../SoftwareKeyboard/Resources/Logo_Ryujinx.png | Bin 0 -> 52972 bytes .../SoftwareKeyboard/SoftwareKeyboardAppear.cs | 119 + .../SoftwareKeyboard/SoftwareKeyboardAppearEx.cs | 100 + .../SoftwareKeyboard/SoftwareKeyboardApplet.cs | 816 +++ .../SoftwareKeyboard/SoftwareKeyboardCalc.cs | 220 + .../SoftwareKeyboard/SoftwareKeyboardCalcEx.cs | 182 + .../SoftwareKeyboard/SoftwareKeyboardConfig.cs | 138 + .../SoftwareKeyboardCustomizeDic.cs | 13 + .../SoftwareKeyboard/SoftwareKeyboardDictSet.cs | 34 + .../SoftwareKeyboard/SoftwareKeyboardInitialize.cs | 26 + .../SoftwareKeyboard/SoftwareKeyboardRenderer.cs | 164 + .../SoftwareKeyboardRendererBase.cs | 606 +++ .../SoftwareKeyboard/SoftwareKeyboardState.cs | 28 + .../SoftwareKeyboard/SoftwareKeyboardUiArgs.cs | 13 + .../SoftwareKeyboard/SoftwareKeyboardUiState.cs | 22 + .../SoftwareKeyboard/SoftwareKeyboardUserWord.cs | 13 + .../HOS/Applets/SoftwareKeyboard/TRef.cs | 19 + .../HOS/Applets/SoftwareKeyboard/TimedAction.cs | 186 + src/Ryujinx.HLE/HOS/ArmProcessContext.cs | 80 + src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs | 89 + .../Demangler/Ast/ArraySubscriptingExpression.cs | 25 + .../HOS/Diagnostics/Demangler/Ast/ArrayType.cs | 59 + .../HOS/Diagnostics/Demangler/Ast/BaseNode.cs | 113 + .../Diagnostics/Demangler/Ast/BinaryExpression.cs | 41 + .../Diagnostics/Demangler/Ast/BracedExpression.cs | 40 + .../Demangler/Ast/BracedRangeExpression.cs | 34 + .../Diagnostics/Demangler/Ast/CallExpression.cs | 24 + .../Diagnostics/Demangler/Ast/CastExpression.cs | 28 + .../Demangler/Ast/ConditionalExpression.cs | 29 + .../Demangler/Ast/ConversionExpression.cs | 24 + .../Demangler/Ast/ConversionOperatorType.cs | 15 + .../Diagnostics/Demangler/Ast/CtorDtorNameType.cs | 24 + .../Demangler/Ast/CtorVtableSpecialName.cs | 24 + .../Diagnostics/Demangler/Ast/DeleteExpression.cs | 33 + .../HOS/Diagnostics/Demangler/Ast/DtorName.cs | 15 + .../Demangler/Ast/DynamicExceptionSpec.cs | 16 + .../Diagnostics/Demangler/Ast/ElaboratedType.cs | 21 + .../Demangler/Ast/EnclosedExpression.cs | 25 + .../Diagnostics/Demangler/Ast/EncodedFunction.cs | 77 + .../Diagnostics/Demangler/Ast/FoldExpression.cs | 48 + .../Demangler/Ast/ForwardTemplateReference.cs | 36 + .../Diagnostics/Demangler/Ast/FunctionParameter.cs | 24 + .../HOS/Diagnostics/Demangler/Ast/FunctionType.cs | 61 + .../Demangler/Ast/GlobalQualifiedName.cs | 15 + .../Demangler/Ast/InitListExpression.cs | 29 + .../Demangler/Ast/IntegerCastExpression.cs | 22 + .../Diagnostics/Demangler/Ast/IntegerLiteral.cs | 42 + .../Diagnostics/Demangler/Ast/LiteralOperator.cs | 16 + .../HOS/Diagnostics/Demangler/Ast/LocalName.cs | 23 + .../Diagnostics/Demangler/Ast/MemberExpression.cs | 25 + .../HOS/Diagnostics/Demangler/Ast/NameType.cs | 29 + .../Demangler/Ast/NameTypeWithTemplateArguments.cs | 27 + .../HOS/Diagnostics/Demangler/Ast/NestedName.cs | 26 + .../HOS/Diagnostics/Demangler/Ast/NewExpression.cs | 55 + .../HOS/Diagnostics/Demangler/Ast/NodeArray.cs | 30 + .../HOS/Diagnostics/Demangler/Ast/NoexceptSpec.cs | 16 + .../Demangler/Ast/PackedTemplateParameter.cs | 39 + .../Ast/PackedTemplateParameterExpansion.cs | 24 + .../HOS/Diagnostics/Demangler/Ast/ParentNode.cs | 17 + .../HOS/Diagnostics/Demangler/Ast/PointerType.cs | 45 + .../Diagnostics/Demangler/Ast/PostfixExpression.cs | 22 + .../Demangler/Ast/PostfixQualifiedType.cs | 20 + .../Diagnostics/Demangler/Ast/PrefixExpression.cs | 22 + .../HOS/Diagnostics/Demangler/Ast/QualifiedName.cs | 23 + .../HOS/Diagnostics/Demangler/Ast/Qualifier.cs | 120 + .../HOS/Diagnostics/Demangler/Ast/ReferenceType.cs | 47 + .../HOS/Diagnostics/Demangler/Ast/SpecialName.cs | 20 + .../Demangler/Ast/SpecialSubstitution.cs | 89 + .../Diagnostics/Demangler/Ast/StdQualifiedName.cs | 15 + .../Diagnostics/Demangler/Ast/TemplateArguments.cs | 26 + .../Diagnostics/Demangler/Ast/ThrowExpression.cs | 20 + .../HOS/Diagnostics/Demangler/Demangler.cs | 3367 ++++++++++++ src/Ryujinx.HLE/HOS/HomebrewRomFsStream.cs | 77 + src/Ryujinx.HLE/HOS/Horizon.cs | 556 ++ src/Ryujinx.HLE/HOS/IdDictionary.cs | 75 + src/Ryujinx.HLE/HOS/Ipc/IpcBuffDesc.cs | 27 + src/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs | 93 + src/Ryujinx.HLE/HOS/Ipc/IpcMagic.cs | 8 + src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs | 283 + src/Ryujinx.HLE/HOS/Ipc/IpcMessageType.cs | 13 + src/Ryujinx.HLE/HOS/Ipc/IpcPtrBuffDesc.cs | 58 + src/Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs | 23 + src/Ryujinx.HLE/HOS/Ipc/ServiceProcessRequest.cs | 4 + .../HOS/Kernel/Common/IKFutureSchedulerObject.cs | 7 + src/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs | 73 + .../HOS/Kernel/Common/KResourceLimit.cs | 188 + .../HOS/Kernel/Common/KSynchronizationObject.cs | 35 + .../HOS/Kernel/Common/KSystemControl.cs | 78 + src/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs | 218 + src/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs | 89 + .../HOS/Kernel/Common/KernelTransfer.cs | 73 + .../HOS/Kernel/Common/LimitableResource.cs | 13 + src/Ryujinx.HLE/HOS/Kernel/Common/MemoryArrange.cs | 12 + src/Ryujinx.HLE/HOS/Kernel/Common/MemroySize.cs | 9 + .../HOS/Kernel/Common/MersenneTwister.cs | 128 + src/Ryujinx.HLE/HOS/Kernel/Ipc/ChannelState.cs | 10 + .../HOS/Kernel/Ipc/KBufferDescriptor.cs | 20 + .../HOS/Kernel/Ipc/KBufferDescriptorTable.cs | 217 + src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs | 144 + src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs | 84 + .../HOS/Kernel/Ipc/KLightClientSession.cs | 14 + .../HOS/Kernel/Ipc/KLightServerSession.cs | 14 + src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightSession.cs | 16 + src/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs | 72 + src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs | 87 + src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs | 1246 +++++ src/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs | 54 + src/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs | 33 + src/Ryujinx.HLE/HOS/Kernel/KernelConstants.cs | 20 + src/Ryujinx.HLE/HOS/Kernel/KernelContext.cs | 160 + src/Ryujinx.HLE/HOS/Kernel/KernelStatic.cs | 73 + .../HOS/Kernel/Memory/AddressSpaceType.cs | 10 + src/Ryujinx.HLE/HOS/Kernel/Memory/DramMemoryMap.cs | 18 + src/Ryujinx.HLE/HOS/Kernel/Memory/KCodeMemory.cs | 169 + src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs | 156 + .../HOS/Kernel/Memory/KMemoryBlockManager.cs | 288 ++ .../HOS/Kernel/Memory/KMemoryBlockSlabManager.cs | 19 + src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs | 36 + .../HOS/Kernel/Memory/KMemoryManager.cs | 65 + .../HOS/Kernel/Memory/KMemoryRegionManager.cs | 242 + src/Ryujinx.HLE/HOS/Kernel/Memory/KPageBitmap.cs | 298 ++ src/Ryujinx.HLE/HOS/Kernel/Memory/KPageHeap.cs | 283 + src/Ryujinx.HLE/HOS/Kernel/Memory/KPageList.cs | 97 + src/Ryujinx.HLE/HOS/Kernel/Memory/KPageNode.cs | 14 + src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs | 229 + .../HOS/Kernel/Memory/KPageTableBase.cs | 3043 +++++++++++ .../HOS/Kernel/Memory/KScopedPageList.cs | 27 + src/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs | 75 + src/Ryujinx.HLE/HOS/Kernel/Memory/KSlabHeap.cs | 50 + .../HOS/Kernel/Memory/KTransferMemory.cs | 130 + .../HOS/Kernel/Memory/MemoryAttribute.cs | 22 + .../HOS/Kernel/Memory/MemoryFillValue.cs | 10 + .../HOS/Kernel/Memory/MemoryPermission.cs | 20 + src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryRegion.cs | 10 + src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryState.cs | 50 + .../HOS/Kernel/Memory/SharedMemoryStorage.cs | 49 + .../HOS/Kernel/Process/CapabilityExtensions.cs | 22 + .../HOS/Kernel/Process/CapabilityType.cs | 19 + .../HOS/Kernel/Process/HleProcessDebugger.cs | 465 ++ .../HOS/Kernel/Process/IProcessContext.cs | 15 + .../HOS/Kernel/Process/IProcessContextFactory.cs | 9 + .../HOS/Kernel/Process/KContextIdManager.cs | 83 + src/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs | 19 + src/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs | 285 ++ src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs | 1196 +++++ .../HOS/Kernel/Process/KProcessCapabilities.cs | 328 ++ src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs | 77 + .../HOS/Kernel/Process/KTlsPageManager.cs | 61 + .../HOS/Kernel/Process/ProcessContext.cs | 34 + .../HOS/Kernel/Process/ProcessContextFactory.cs | 12 + .../HOS/Kernel/Process/ProcessCreationFlags.cs | 41 + .../HOS/Kernel/Process/ProcessCreationInfo.cs | 37 + .../HOS/Kernel/Process/ProcessExecutionContext.cs | 46 + src/Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs | 14 + .../HOS/Kernel/Process/ProcessTamperInfo.cs | 24 + .../Kernel/SupervisorCall/CodeMemoryOperation.cs | 10 + .../HOS/Kernel/SupervisorCall/InfoType.cs | 34 + .../HOS/Kernel/SupervisorCall/MemoryInfo.cs | 37 + .../Kernel/SupervisorCall/PointerSizedAttribute.cs | 9 + .../HOS/Kernel/SupervisorCall/SvcAttribute.cs | 15 + .../HOS/Kernel/SupervisorCall/SvcImplAttribute.cs | 9 + .../HOS/Kernel/SupervisorCall/Syscall.cs | 3010 +++++++++++ .../HOS/Kernel/SupervisorCall/SyscallHandler.cs | 44 + .../HOS/Kernel/SupervisorCall/ThreadContext.cs | 22 + .../HOS/Kernel/Threading/ArbitrationType.cs | 9 + .../HOS/Kernel/Threading/KAddressArbiter.cs | 581 +++ .../HOS/Kernel/Threading/KConditionVariable.cs | 70 + .../HOS/Kernel/Threading/KCriticalSection.cs | 64 + src/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs | 14 + .../HOS/Kernel/Threading/KPriorityQueue.cs | 286 ++ .../HOS/Kernel/Threading/KReadableEvent.cs | 65 + src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs | 661 +++ .../HOS/Kernel/Threading/KSynchronization.cs | 142 + src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs | 1438 ++++++ .../HOS/Kernel/Threading/KThreadContext.cs | 33 + .../HOS/Kernel/Threading/KWritableEvent.cs | 25 + src/Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs | 9 + .../HOS/Kernel/Threading/ThreadSchedState.cs | 20 + src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs | 10 + src/Ryujinx.HLE/HOS/LibHacHorizonManager.cs | 120 + src/Ryujinx.HLE/HOS/ModLoader.cs | 761 +++ src/Ryujinx.HLE/HOS/ResultCode.cs | 12 + src/Ryujinx.HLE/HOS/ServiceCtx.cs | 40 + .../HOS/Services/Account/Acc/AccountManager.cs | 241 + .../Services/Account/Acc/AccountSaveDataManager.cs | 76 + .../Acc/AccountService/IManagerForApplication.cs | 75 + .../Acc/AccountService/IManagerForSystemService.cs | 47 + .../Account/Acc/AccountService/IProfile.cs | 40 + .../Account/Acc/AccountService/IProfileEditor.cs | 54 + .../Account/Acc/AccountService/ManagerServer.cs | 187 + .../Account/Acc/AccountService/ProfileServer.cs | 114 + .../Account/Acc/ApplicationServiceServer.cs | 254 + .../Account/Acc/AsyncContext/AsyncExecution.cs | 56 + .../HOS/Services/Account/Acc/DefaultUserImage.jpg | Bin 0 -> 49000 bytes .../Account/Acc/IAccountServiceForAdministrator.cs | 129 + .../Account/Acc/IAccountServiceForApplication.cs | 200 + .../Account/Acc/IAccountServiceForSystemService.cs | 107 + .../HOS/Services/Account/Acc/IAsyncContext.cs | 79 + .../Acc/IAsyncNetworkServiceLicenseKindContext.cs | 38 + .../Account/Acc/IBaasAccessTokenAccessor.cs | 8 + .../Account/Acc/ProfilesJsonSerializerContext.cs | 11 + .../Account/Acc/Types/AccountServiceFlag.cs | 10 + .../HOS/Services/Account/Acc/Types/AccountState.cs | 12 + .../Account/Acc/Types/NetworkServiceLicenseKind.cs | 8 + .../HOS/Services/Account/Acc/Types/ProfilesJson.cs | 10 + .../HOS/Services/Account/Acc/Types/UserId.cs | 64 + .../HOS/Services/Account/Acc/Types/UserProfile.cs | 87 + .../Services/Account/Acc/Types/UserProfileJson.cs | 12 + .../HOS/Services/Account/Dauth/IService.cs | 8 + src/Ryujinx.HLE/HOS/Services/Account/ResultCode.cs | 24 + .../ILibraryAppletProxy.cs | 105 + .../ISystemAppletProxy.cs | 104 + .../LibraryAppletCreator/ILibraryAppletAccessor.cs | 261 + .../LibraryAppletProxy/AppletStandalone.cs | 16 + .../ILibraryAppletSelfAccessor.cs | 78 + .../IProcessWindingController.cs | 24 + .../SystemAppletProxy/IAppletCommonFunctions.cs | 7 + .../SystemAppletProxy/IApplicationCreator.cs | 7 + .../SystemAppletProxy/IAudioController.cs | 66 + .../SystemAppletProxy/ICommonStateGetter.cs | 285 ++ .../SystemAppletProxy/IDebugFunctions.cs | 7 + .../SystemAppletProxy/IDisplayController.cs | 106 + .../SystemAppletProxy/IGlobalStateController.cs | 7 + .../SystemAppletProxy/IHomeMenuFunctions.cs | 48 + .../SystemAppletProxy/ILibraryAppletCreator.cs | 91 + .../SystemAppletProxy/ISelfController.cs | 432 ++ .../SystemAppletProxy/IWindowController.cs | 36 + .../SystemAppletProxy/Types/AlbumReportOption.cs | 10 + .../SystemAppletProxy/Types/AppletMessage.cs | 36 + .../SystemAppletProxy/Types/FocusState.cs | 8 + .../SystemAppletProxy/Types/OperationMode.cs | 8 + .../Types/WirelessPriorityMode.cs | 9 + .../HOS/Services/Am/AppletAE/AppletFifo.cs | 120 + .../HOS/Services/Am/AppletAE/AppletSession.cs | 77 + .../Am/AppletAE/IAllSystemAppletProxiesService.cs | 29 + .../HOS/Services/Am/AppletAE/IAppletFifo.cs | 10 + .../HOS/Services/Am/AppletAE/IStorage.cs | 23 + .../HOS/Services/Am/AppletAE/IStorageAccessor.cs | 86 + .../Services/Am/AppletAE/Storage/StorageHelper.cs | 28 + .../HOS/Services/Am/AppletAE/Types/AppletId.cs | 27 + .../Am/AppletAE/Types/AppletIdentityInfo.cs | 12 + .../Am/AppletAE/Types/AppletProcessLaunchReason.cs | 12 + .../Am/AppletAE/Types/LibraryAppletInfo.cs | 11 + .../Am/AppletAE/Types/LibraryAppletMode.cs | 14 + .../ApplicationProxy/IApplicationFunctions.cs | 675 +++ .../ApplicationProxy/Types/LaunchParameterKind.cs | 9 + .../ApplicationProxy/Types/ProgramSpecifyKind.cs | 9 + .../ApplicationProxyService/IApplicationProxy.cs | 87 + .../Am/AppletOE/IApplicationProxyService.cs | 19 + .../HOS/Services/Am/Idle/IPolicyManagerSystem.cs | 8 + .../HOS/Services/Am/Omm/IOperationModeManager.cs | 8 + src/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs | 30 + .../HOS/Services/Am/Spsm/IPowerStateInterface.cs | 8 + src/Ryujinx.HLE/HOS/Services/Am/Tcap/IManager.cs | 8 + src/Ryujinx.HLE/HOS/Services/Apm/IManager.cs | 43 + .../HOS/Services/Apm/IManagerPrivileged.cs | 19 + src/Ryujinx.HLE/HOS/Services/Apm/ISession.cs | 45 + src/Ryujinx.HLE/HOS/Services/Apm/ISystemManager.cs | 42 + src/Ryujinx.HLE/HOS/Services/Apm/ManagerServer.cs | 31 + .../HOS/Services/Apm/PerformanceState.cs | 25 + src/Ryujinx.HLE/HOS/Services/Apm/ResultCode.cs | 12 + src/Ryujinx.HLE/HOS/Services/Apm/SessionServer.cs | 58 + .../HOS/Services/Apm/SystemManagerServer.cs | 28 + .../HOS/Services/Apm/Types/CpuBoostMode.cs | 9 + .../Services/Apm/Types/PerformanceConfiguration.cs | 22 + .../HOS/Services/Apm/Types/PerformanceMode.cs | 8 + .../HOS/Services/Arp/ApplicationLaunchProperty.cs | 43 + src/Ryujinx.HLE/HOS/Services/Arp/IReader.cs | 8 + src/Ryujinx.HLE/HOS/Services/Arp/IWriter.cs | 8 + src/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs | 76 + .../HOS/Services/Audio/AudioIn/AudioIn.cs | 108 + .../HOS/Services/Audio/AudioIn/AudioInServer.cs | 204 + .../HOS/Services/Audio/AudioIn/IAudioIn.cs | 34 + .../HOS/Services/Audio/AudioInManager.cs | 41 + .../HOS/Services/Audio/AudioInManagerServer.cs | 235 + .../HOS/Services/Audio/AudioOut/AudioOut.cs | 108 + .../HOS/Services/Audio/AudioOut/AudioOutServer.cs | 185 + .../HOS/Services/Audio/AudioOut/IAudioOut.cs | 33 + .../HOS/Services/Audio/AudioOutManager.cs | 41 + .../HOS/Services/Audio/AudioOutManagerServer.cs | 162 + .../Services/Audio/AudioRenderer/AudioDevice.cs | 172 + .../Audio/AudioRenderer/AudioDeviceServer.cs | 320 ++ .../Audio/AudioRenderer/AudioKernelEvent.cs | 25 + .../Services/Audio/AudioRenderer/AudioRenderer.cs | 122 + .../Audio/AudioRenderer/AudioRendererServer.cs | 217 + .../Services/Audio/AudioRenderer/IAudioDevice.cs | 18 + .../Services/Audio/AudioRenderer/IAudioRenderer.cs | 22 + .../HOS/Services/Audio/AudioRendererManager.cs | 67 + .../Services/Audio/AudioRendererManagerServer.cs | 116 + .../Audio/HardwareOpusDecoderManager/Decoder.cs | 27 + .../HardwareOpusDecoderManager/DecoderCommon.cs | 92 + .../Audio/HardwareOpusDecoderManager/IDecoder.cs | 11 + .../IHardwareOpusDecoder.cs | 116 + .../MultiSampleDecoder.cs | 28 + .../HOS/Services/Audio/IAudioController.cs | 8 + .../HOS/Services/Audio/IAudioInManager.cs | 12 + .../HOS/Services/Audio/IAudioInManagerForApplet.cs | 8 + .../Services/Audio/IAudioInManagerForDebugger.cs | 8 + .../HOS/Services/Audio/IAudioOutManager.cs | 12 + .../Services/Audio/IAudioOutManagerForApplet.cs | 8 + .../Services/Audio/IAudioOutManagerForDebugger.cs | 8 + .../HOS/Services/Audio/IAudioRendererManager.cs | 19 + .../Audio/IAudioRendererManagerForApplet.cs | 8 + .../Audio/IAudioRendererManagerForDebugger.cs | 8 + .../HOS/Services/Audio/IAudioSnoopManager.cs | 8 + .../Services/Audio/IFinalOutputRecorderManager.cs | 8 + .../Audio/IFinalOutputRecorderManagerForApplet.cs | 8 + .../IFinalOutputRecorderManagerForDebugger.cs | 8 + .../Services/Audio/IHardwareOpusDecoderManager.cs | 205 + src/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs | 21 + .../HOS/Services/Audio/Types/OpusDecoderFlags.cs | 11 + .../Audio/Types/OpusMultiStreamParameters.cs | 15 + .../Audio/Types/OpusMultiStreamParametersEx.cs | 19 + .../HOS/Services/Audio/Types/OpusPacketHeader.cs | 23 + .../HOS/Services/Audio/Types/OpusParametersEx.cs | 15 + .../HOS/Services/Bcat/IServiceCreator.cs | 85 + src/Ryujinx.HLE/HOS/Services/Bcat/ResultCode.cs | 29 + .../Services/Bcat/ServiceCreator/IBcatService.cs | 18 + .../IDeliveryCacheDirectoryService.cs | 65 + .../ServiceCreator/IDeliveryCacheFileService.cs | 78 + .../IDeliveryCacheProgressService.cs | 63 + .../ServiceCreator/IDeliveryCacheStorageService.cs | 74 + .../Types/DeliveryCacheProgressImpl.cs | 18 + .../HOS/Services/Bgtc/IStateControlService.cs | 8 + src/Ryujinx.HLE/HOS/Services/Bgtc/ITaskService.cs | 8 + .../BluetoothDriver/BluetoothEventManager.cs | 25 + .../HOS/Services/Bluetooth/IBluetoothDriver.cs | 103 + .../HOS/Services/Bluetooth/IBluetoothUser.cs | 30 + .../BluetoothManager/BtmUser/IBtmUserCore.cs | 128 + .../HOS/Services/BluetoothManager/IBtm.cs | 8 + .../HOS/Services/BluetoothManager/IBtmDebug.cs | 8 + .../HOS/Services/BluetoothManager/IBtmSystem.cs | 8 + .../HOS/Services/BluetoothManager/IBtmUser.cs | 19 + .../HOS/Services/BluetoothManager/ResultCode.cs | 10 + .../HOS/Services/Caps/CaptureManager.cs | 134 + .../HOS/Services/Caps/IAlbumAccessorService.cs | 8 + .../HOS/Services/Caps/IAlbumApplicationService.cs | 69 + .../HOS/Services/Caps/IAlbumControlService.cs | 15 + .../Services/Caps/IScreenShotApplicationService.cs | 98 + .../HOS/Services/Caps/IScreenShotControlService.cs | 8 + .../HOS/Services/Caps/IScreenshotService.cs | 8 + src/Ryujinx.HLE/HOS/Services/Caps/ResultCode.cs | 18 + .../HOS/Services/Caps/Types/AlbumFileDateTime.cs | 16 + .../Services/Caps/Types/AlbumImageOrientation.cs | 10 + .../HOS/Services/Caps/Types/AlbumStorage.cs | 8 + .../Services/Caps/Types/ApplicationAlbumEntry.cs | 17 + .../HOS/Services/Caps/Types/ContentType.cs | 10 + .../HOS/Services/Caps/Types/ScreenShotAttribute.cs | 15 + src/Ryujinx.HLE/HOS/Services/Cec/ICecManager.cs | 8 + .../HOS/Services/CommandCmifAttribute.cs | 12 + .../HOS/Services/CommandTIpcAttribute.cs | 12 + .../HOS/Services/DisposableIpcService.cs | 22 + src/Ryujinx.HLE/HOS/Services/DummyService.cs | 12 + .../HOS/Services/Ectx/IReaderForSystem.cs | 8 + .../HOS/Services/Ectx/IWriterForApplication.cs | 8 + .../HOS/Services/Ectx/IWriterForSystem.cs | 8 + src/Ryujinx.HLE/HOS/Services/Erpt/IContext.cs | 8 + src/Ryujinx.HLE/HOS/Services/Erpt/ISession.cs | 8 + src/Ryujinx.HLE/HOS/Services/Es/IETicketService.cs | 8 + src/Ryujinx.HLE/HOS/Services/Eupld/IControl.cs | 8 + src/Ryujinx.HLE/HOS/Services/Eupld/IRequest.cs | 8 + .../HOS/Services/Fatal/IPrivateService.cs | 8 + src/Ryujinx.HLE/HOS/Services/Fatal/IService.cs | 147 + .../HOS/Services/Fatal/Types/CpuContext32.cs | 25 + .../HOS/Services/Fatal/Types/CpuContext64.cs | 24 + .../HOS/Services/Fatal/Types/FatalPolicy.cs | 9 + .../HOS/Services/Friend/IServiceCreator.cs | 55 + src/Ryujinx.HLE/HOS/Services/Friend/ResultCode.cs | 14 + .../ServiceCreator/FriendService/Types/Friend.cs | 29 + .../FriendService/Types/FriendFilter.cs | 24 + .../FriendService/Types/PresenceStatus.cs | 9 + .../FriendService/Types/PresenceStatusFilter.cs | 10 + .../FriendService/Types/UserPresence.cs | 34 + .../ServiceCreator/IDaemonSuspendSessionService.cs | 12 + .../Friend/ServiceCreator/IFriendService.cs | 352 ++ .../Friend/ServiceCreator/INotificationService.cs | 178 + .../NotificationEventHandler.cs | 83 + .../Types/NotificationEventType.cs | 9 + .../NotificationService/Types/NotificationInfo.cs | 13 + .../Types/FriendServicePermissionLevel.cs | 19 + .../Types/PlayHistoryRegistrationKey.cs | 16 + .../Fs/FileSystemProxy/FileSystemProxyHelper.cs | 161 + .../HOS/Services/Fs/FileSystemProxy/IDirectory.cs | 52 + .../HOS/Services/Fs/FileSystemProxy/IFile.cs | 95 + .../HOS/Services/Fs/FileSystemProxy/IFileSystem.cs | 213 + .../HOS/Services/Fs/FileSystemProxy/IStorage.cs | 65 + src/Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs | 58 + .../HOS/Services/Fs/IFileSystemProxy.cs | 1308 +++++ .../HOS/Services/Fs/IFileSystemProxyForLoader.cs | 8 + .../HOS/Services/Fs/IMultiCommitManager.cs | 44 + .../HOS/Services/Fs/IProgramRegistry.cs | 8 + .../HOS/Services/Fs/ISaveDataInfoReader.cs | 41 + src/Ryujinx.HLE/HOS/Services/Fs/ResultCode.cs | 16 + .../HOS/Services/Fs/Types/FileSystemType.cs | 12 + src/Ryujinx.HLE/HOS/Services/Grc/IGrcService.cs | 8 + .../HOS/Services/Grc/IRemoteVideoTransfer.cs | 8 + src/Ryujinx.HLE/HOS/Services/Hid/Hid.cs | 107 + .../HOS/Services/Hid/HidDevices/BaseDevice.cs | 14 + .../HOS/Services/Hid/HidDevices/DebugPadDevice.cs | 28 + .../HOS/Services/Hid/HidDevices/KeyboardDevice.cs | 35 + .../HOS/Services/Hid/HidDevices/MouseDevice.cs | 36 + .../HOS/Services/Hid/HidDevices/NpadDevices.cs | 635 +++ .../HOS/Services/Hid/HidDevices/TouchDevice.cs | 48 + .../Hid/HidDevices/Types/ControllerConfig.cs | 8 + .../Services/Hid/HidDevices/Types/GamepadInput.cs | 10 + .../Hid/HidDevices/Types/JoystickPosition.cs | 8 + .../Services/Hid/HidDevices/Types/KeyboardInput.cs | 8 + .../Services/Hid/HidDevices/Types/SixAxisInput.cs | 13 + .../Services/Hid/HidDevices/Types/TouchPoint.cs | 14 + .../HOS/Services/Hid/HidServer/HidUtils.cs | 46 + .../Hid/HidServer/IActiveVibrationDeviceList.cs | 16 + .../HOS/Services/Hid/HidServer/IAppletResource.cs | 35 + .../Types/Npad/NpadHandheldActivationMode.cs | 9 + .../Hid/HidServer/Types/Npad/NpadJoyDeviceType.cs | 8 + .../Types/SixAxis/AccelerometerParameters.cs | 8 + .../Types/SixAxis/GyroscopeZeroDriftMode.cs | 9 + .../Types/SixAxis/SensorFusionParameters.cs | 8 + .../Types/Vibration/VibrationDeviceHandle.cs | 10 + .../Types/Vibration/VibrationDevicePosition.cs | 9 + .../Types/Vibration/VibrationDeviceType.cs | 9 + .../Types/Vibration/VibrationDeviceValue.cs | 8 + .../HidServer/Types/Vibration/VibrationValue.cs | 24 + .../HOS/Services/Hid/IHidDebugServer.cs | 8 + src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs | 1800 +++++++ .../HOS/Services/Hid/IHidSystemServer.cs | 76 + src/Ryujinx.HLE/HOS/Services/Hid/IHidbusServer.cs | 8 + src/Ryujinx.HLE/HOS/Services/Hid/ISystemServer.cs | 8 + .../HOS/Services/Hid/Irs/IIrSensorServer.cs | 240 + .../HOS/Services/Hid/Irs/IIrSensorSystemServer.cs | 8 + src/Ryujinx.HLE/HOS/Services/Hid/Irs/ResultCode.cs | 15 + .../Hid/Irs/Types/ImageTransferProcessorState.cs | 12 + .../HOS/Services/Hid/Irs/Types/IrCameraHandle.cs | 12 + .../Irs/Types/PackedClusteringProcessorConfig.cs | 25 + .../Types/PackedImageTransferProcessorConfig.cs | 19 + .../Hid/Irs/Types/PackedMomentProcessorConfig.cs | 23 + .../Irs/Types/PackedTeraPluginProcessorConfig.cs | 14 + src/Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs | 15 + .../HOS/Services/Hid/Types/AppletFooterUiType.cs | 30 + .../HOS/Services/Hid/Types/HidVector.cs | 9 + .../HOS/Services/Hid/Types/Npad/ControllerKeys.cs | 45 + .../HOS/Services/Hid/Types/Npad/ControllerType.cs | 19 + .../HOS/Services/Hid/Types/Npad/NpadColor.cs | 37 + .../HOS/Services/Hid/Types/Npad/NpadIdType.cs | 16 + .../HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs | 13 + .../HOS/Services/Hid/Types/Npad/PlayerIndex.cs | 17 + .../HOS/Services/Hid/Types/NpadJoyHoldType.cs | 8 + .../Types/SharedMemory/Common/AnalogStickState.cs | 8 + .../Hid/Types/SharedMemory/Common/AtomicStorage.cs | 26 + .../SharedMemory/Common/ISampledDataStruct.cs | 65 + .../Hid/Types/SharedMemory/Common/RingLifo.cs | 149 + .../SharedMemory/DebugPad/DebugPadAttribute.cs | 11 + .../Types/SharedMemory/DebugPad/DebugPadButton.cs | 24 + .../Types/SharedMemory/DebugPad/DebugPadState.cs | 15 + .../Hid/Types/SharedMemory/Keyboard/KeyboardKey.cs | 29 + .../SharedMemory/Keyboard/KeyboardKeyShift.cs | 138 + .../SharedMemory/Keyboard/KeyboardModifier.cs | 20 + .../Types/SharedMemory/Keyboard/KeyboardState.cs | 13 + .../Hid/Types/SharedMemory/Mouse/MouseAttribute.cs | 12 + .../Hid/Types/SharedMemory/Mouse/MouseButton.cs | 15 + .../Hid/Types/SharedMemory/Mouse/MouseState.cs | 19 + .../Hid/Types/SharedMemory/Npad/DeviceType.cs | 29 + .../Hid/Types/SharedMemory/Npad/NpadAttribute.cs | 16 + .../Types/SharedMemory/Npad/NpadBatteryLevel.cs | 11 + .../Hid/Types/SharedMemory/Npad/NpadButton.cs | 44 + .../Types/SharedMemory/Npad/NpadColorAttribute.cs | 9 + .../Hid/Types/SharedMemory/Npad/NpadCommonState.cs | 16 + .../SharedMemory/Npad/NpadFullKeyColorState.cs | 9 + .../Types/SharedMemory/Npad/NpadGcTriggerState.cs | 15 + .../Types/SharedMemory/Npad/NpadInternalState.cs | 65 + .../SharedMemory/Npad/NpadJoyAssignmentMode.cs | 8 + .../Types/SharedMemory/Npad/NpadJoyColorState.cs | 11 + .../Hid/Types/SharedMemory/Npad/NpadLarkType.cs | 11 + .../Hid/Types/SharedMemory/Npad/NpadLuciaType.cs | 10 + .../Hid/Types/SharedMemory/Npad/NpadState.cs | 18 + .../Hid/Types/SharedMemory/Npad/NpadStyleTag.cs | 76 + .../Npad/NpadSystemButtonProperties.cs | 11 + .../SharedMemory/Npad/NpadSystemProperties.cs | 24 + .../SharedMemory/Npad/SixAxisSensorAttribute.cs | 12 + .../Types/SharedMemory/Npad/SixAxisSensorState.cs | 19 + .../Hid/Types/SharedMemory/SharedMemory.cs | 66 + .../SharedMemory/TouchScreen/TouchAttribute.cs | 12 + .../SharedMemory/TouchScreen/TouchScreenState.cs | 15 + .../Types/SharedMemory/TouchScreen/TouchState.cs | 19 + .../HOS/Services/Ins/IReceiverManager.cs | 8 + src/Ryujinx.HLE/HOS/Services/Ins/ISenderManager.cs | 8 + src/Ryujinx.HLE/HOS/Services/IpcService.cs | 284 ++ src/Ryujinx.HLE/HOS/Services/Lbl/ILblController.cs | 92 + .../HOS/Services/Lbl/LblControllerServer.cs | 54 + .../HOS/Services/Ldn/IMonitorServiceCreator.cs | 8 + .../HOS/Services/Ldn/ISystemServiceCreator.cs | 8 + .../HOS/Services/Ldn/IUserServiceCreator.cs | 19 + .../HOS/Services/Ldn/Lp2p/IServiceCreator.cs | 9 + .../HOS/Services/Ldn/NetworkInterface.cs | 59 + src/Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs | 16 + .../HOS/Services/Ldn/Types/NetworkState.cs | 13 + .../IUserLocalCommunicationService.cs | 88 + .../HOS/Services/Loader/IDebugMonitorInterface.cs | 8 + .../Services/Loader/IProcessManagerInterface.cs | 8 + .../HOS/Services/Loader/IShellInterface.cs | 8 + src/Ryujinx.HLE/HOS/Services/Loader/ResultCode.cs | 43 + src/Ryujinx.HLE/HOS/Services/Mig/IService.cs | 8 + src/Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs | 328 ++ .../HOS/Services/Mii/DatabaseSessionMetadata.cs | 24 + src/Ryujinx.HLE/HOS/Services/Mii/Helper.cs | 48 + .../HOS/Services/Mii/IImageDatabaseService.cs | 41 + src/Ryujinx.HLE/HOS/Services/Mii/IStaticService.cs | 32 + .../HOS/Services/Mii/MiiDatabaseManager.cs | 501 ++ src/Ryujinx.HLE/HOS/Services/Mii/ResultCode.cs | 30 + .../Mii/StaticService/DatabaseServiceImpl.cs | 266 + .../Services/Mii/StaticService/IDatabaseService.cs | 425 ++ src/Ryujinx.HLE/HOS/Services/Mii/Types/Age.cs | 10 + .../HOS/Services/Mii/Types/BeardType.cs | 15 + src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfo.cs | 329 ++ .../HOS/Services/Mii/Types/CharInfoElement.cs | 21 + .../HOS/Services/Mii/Types/CommonColor.cs | 9 + src/Ryujinx.HLE/HOS/Services/Mii/Types/CoreData.cs | 911 ++++ src/Ryujinx.HLE/HOS/Services/Mii/Types/CreateId.cs | 45 + .../HOS/Services/Mii/Types/DefaultMii.cs | 197 + src/Ryujinx.HLE/HOS/Services/Mii/Types/EyeType.cs | 69 + .../HOS/Services/Mii/Types/EyebrowType.cs | 33 + .../HOS/Services/Mii/Types/FacelineColor.cs | 19 + .../HOS/Services/Mii/Types/FacelineMake.cs | 21 + .../HOS/Services/Mii/Types/FacelineType.cs | 21 + .../HOS/Services/Mii/Types/FacelineWrinkle.cs | 21 + .../HOS/Services/Mii/Types/FontRegion.cs | 10 + src/Ryujinx.HLE/HOS/Services/Mii/Types/Gender.cs | 12 + .../HOS/Services/Mii/Types/GlassType.cs | 29 + src/Ryujinx.HLE/HOS/Services/Mii/Types/HairFlip.cs | 11 + src/Ryujinx.HLE/HOS/Services/Mii/Types/HairType.cs | 141 + src/Ryujinx.HLE/HOS/Services/Mii/Types/IElement.cs | 9 + .../HOS/Services/Mii/Types/IStoredData.cs | 15 + src/Ryujinx.HLE/HOS/Services/Mii/Types/MoleType.cs | 11 + .../HOS/Services/Mii/Types/MouthType.cs | 45 + .../HOS/Services/Mii/Types/MustacheType.cs | 15 + src/Ryujinx.HLE/HOS/Services/Mii/Types/Nickname.cs | 120 + .../Services/Mii/Types/NintendoFigurineDatabase.cs | 254 + src/Ryujinx.HLE/HOS/Services/Mii/Types/NoseType.cs | 27 + src/Ryujinx.HLE/HOS/Services/Mii/Types/Race.cs | 10 + .../HOS/Services/Mii/Types/RandomMiiConstants.cs | 2254 ++++++++ src/Ryujinx.HLE/HOS/Services/Mii/Types/Source.cs | 8 + .../HOS/Services/Mii/Types/SourceFlag.cs | 12 + .../HOS/Services/Mii/Types/SpecialMiiKeyCode.cs | 17 + .../HOS/Services/Mii/Types/StoreData.cs | 230 + .../HOS/Services/Mii/Types/StoreDataElement.cs | 21 + .../HOS/Services/Mii/Types/Ver3StoreData.cs | 17 + src/Ryujinx.HLE/HOS/Services/Mii/UtilityImpl.cs | 75 + src/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs | 196 + .../Services/Mm/Types/MultiMediaOperationType.cs | 10 + .../HOS/Services/Mm/Types/MultiMediaSession.cs | 24 + .../HOS/Services/Mnpp/IServiceForApplication.cs | 63 + src/Ryujinx.HLE/HOS/Services/Mnpp/ResultCode.cs | 13 + .../HOS/Services/Ncm/IContentManager.cs | 8 + .../Services/Ncm/Lr/ILocationResolverManager.cs | 22 + .../LocationResolverManager/ILocationResolver.cs | 254 + src/Ryujinx.HLE/HOS/Services/Ncm/Lr/ResultCode.cs | 20 + .../HOS/Services/News/IServiceCreator.cs | 12 + src/Ryujinx.HLE/HOS/Services/Nfc/IAmManager.cs | 8 + src/Ryujinx.HLE/HOS/Services/Nfc/ISystemManager.cs | 19 + src/Ryujinx.HLE/HOS/Services/Nfc/IUserManager.cs | 19 + .../HOS/Services/Nfc/Mifare/IUserManager.cs | 8 + .../HOS/Services/Nfc/NfcManager/INfc.cs | 63 + .../Nfc/NfcManager/Types/NfcPermissionLevel.cs | 8 + .../HOS/Services/Nfc/NfcManager/Types/State.cs | 8 + .../Nfc/Nfp/AmiiboJsonSerializerContext.cs | 10 + .../HOS/Services/Nfc/Nfp/IDebugManager.cs | 19 + .../HOS/Services/Nfc/Nfp/ISystemManager.cs | 19 + .../HOS/Services/Nfc/Nfp/IUserManager.cs | 19 + .../HOS/Services/Nfc/Nfp/NfpManager/INfp.cs | 1000 ++++ .../Nfc/Nfp/NfpManager/Types/AmiiboConstants.cs | 8 + .../Nfc/Nfp/NfpManager/Types/CommonInfo.cs | 17 + .../Nfc/Nfp/NfpManager/Types/DeviceType.cs | 7 + .../Services/Nfc/Nfp/NfpManager/Types/ModelInfo.cs | 16 + .../Nfc/Nfp/NfpManager/Types/MountTarget.cs | 9 + .../Services/Nfc/Nfp/NfpManager/Types/NfpDevice.cs | 23 + .../Nfc/Nfp/NfpManager/Types/NfpDeviceState.cs | 13 + .../Nfc/Nfp/NfpManager/Types/NfpPermissionLevel.cs | 9 + .../Nfc/Nfp/NfpManager/Types/RegisterInfo.cs | 19 + .../HOS/Services/Nfc/Nfp/NfpManager/Types/State.cs | 8 + .../Services/Nfc/Nfp/NfpManager/Types/TagInfo.cs | 16 + .../Nfc/Nfp/NfpManager/Types/VirtualAmiiboFile.cs | 22 + src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs | 18 + .../HOS/Services/Nfc/Nfp/VirtualAmiibo.cs | 204 + src/Ryujinx.HLE/HOS/Services/Ngct/IService.cs | 22 + .../HOS/Services/Ngct/IServiceWithManagementApi.cs | 22 + src/Ryujinx.HLE/HOS/Services/Ngct/NgctServer.cs | 92 + .../HOS/Services/Nifm/IStaticService.cs | 30 + src/Ryujinx.HLE/HOS/Services/Nifm/ResultCode.cs | 15 + .../GeneralService/GeneralServiceManager.cs | 30 + .../GeneralService/Types/GeneralServiceDetail.cs | 8 + .../Services/Nifm/StaticService/IGeneralService.cs | 203 + .../HOS/Services/Nifm/StaticService/IRequest.cs | 142 + .../Nifm/StaticService/Types/DnsSetting.cs | 31 + .../StaticService/Types/InternetConnectionState.cs | 11 + .../Types/InternetConnectionStatus.cs | 12 + .../StaticService/Types/InternetConnectionType.cs | 9 + .../Nifm/StaticService/Types/IpAddressSetting.cs | 24 + .../Nifm/StaticService/Types/IpSettingData.cs | 13 + .../Nifm/StaticService/Types/IpV4Address.cs | 24 + .../Nifm/StaticService/Types/NetworkProfileData.cs | 17 + .../Nifm/StaticService/Types/ProxySetting.cs | 27 + .../StaticService/Types/WirelessSettingData.cs | 15 + .../HOS/Services/Nim/INetworkInstallManager.cs | 8 + .../HOS/Services/Nim/IShopServiceAccessServer.cs | 21 + .../Nim/IShopServiceAccessServerInterface.cs | 44 + .../Nim/IShopServiceAccessSystemInterface.cs | 8 + .../HOS/Services/Nim/IShopServiceAccessor.cs | 42 + .../HOS/Services/Nim/IShopServiceAsync.cs | 7 + .../HOS/Services/Nim/IShopServiceManager.cs | 8 + .../HOS/Services/Nim/Ntc/IStaticService.cs | 24 + .../IEnsureNetworkClockAvailabilityService.cs | 77 + src/Ryujinx.HLE/HOS/Services/Nim/ResultCode.cs | 12 + .../INotificationServicesForApplication.cs | 8 + .../Notification/INotificationServicesForSystem.cs | 8 + src/Ryujinx.HLE/HOS/Services/Npns/INpnsSystem.cs | 8 + src/Ryujinx.HLE/HOS/Services/Npns/INpnsUser.cs | 8 + .../HOS/Services/Ns/Aoc/IAddOnContentManager.cs | 346 ++ .../HOS/Services/Ns/Aoc/IContentsServiceManager.cs | 7 + .../HOS/Services/Ns/Aoc/IPurchaseEventManager.cs | 68 + src/Ryujinx.HLE/HOS/Services/Ns/Aoc/ResultCode.cs | 13 + .../Services/Ns/IApplicationManagerInterface.cs | 28 + .../HOS/Services/Ns/IDevelopInterface.cs | 8 + .../Ns/IReadOnlyApplicationControlDataInterface.cs | 26 + .../HOS/Services/Ns/IServiceGetterInterface.cs | 30 + .../HOS/Services/Ns/ISystemUpdateInterface.cs | 8 + .../Services/Ns/IVulnerabilityManagerInterface.cs | 8 + src/Ryujinx.HLE/HOS/Services/Nv/Host1xContext.cs | 32 + .../HOS/Services/Nv/INvDrvDebugFSServices.cs | 8 + src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs | 598 +++ src/Ryujinx.HLE/HOS/Services/Nv/INvGemControl.cs | 8 + src/Ryujinx.HLE/HOS/Services/Nv/INvGemCoreDump.cs | 8 + .../HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs | 94 + .../NvHostAsGpu/NvHostAsGpuDeviceFile.cs | 401 ++ .../NvHostAsGpu/Types/AddressSpaceContext.cs | 190 + .../NvHostAsGpu/Types/AddressSpaceFlags.cs | 11 + .../NvHostAsGpu/Types/AllocSpaceArguments.cs | 14 + .../NvHostAsGpu/Types/BindChannelArguments.cs | 10 + .../NvHostAsGpu/Types/FreeSpaceArguments.cs | 12 + .../NvHostAsGpu/Types/GetVaRegionsArguments.cs | 23 + .../NvHostAsGpu/Types/InitializeExArguments.cs | 16 + .../NvHostAsGpu/Types/MapBufferExArguments.cs | 16 + .../NvHostAsGpu/Types/RemapArguments.cs | 15 + .../NvHostAsGpu/Types/UnmapBufferArguments.cs | 9 + .../NvHostChannel/ChannelInitialization.cs | 1361 +++++ .../NvHostChannel/NvHostChannelDeviceFile.cs | 574 +++ .../NvHostChannel/NvHostGpuDeviceFile.cs | 105 + .../NvHostChannel/Types/AllocGpfifoExArguments.cs | 17 + .../NvHostChannel/Types/AllocObjCtxArguments.cs | 12 + .../NvHostChannel/Types/GetParameterArguments.cs | 11 + .../Types/MapCommandBufferArguments.cs | 21 + .../NvDrvServices/NvHostChannel/Types/NvChannel.cs | 11 + .../NvHostChannel/Types/NvChannelPriority.cs | 9 + .../Types/SetErrorNotifierArguments.cs | 13 + .../NvHostChannel/Types/SubmitArguments.cs | 40 + .../NvHostChannel/Types/SubmitGpfifoArguments.cs | 14 + .../NvHostChannel/Types/SubmitGpfifoFlags.cs | 15 + .../NvHostChannel/Types/ZcullBindArguments.cs | 12 + .../NvHostCtrl/NvHostCtrlDeviceFile.cs | 540 ++ .../NvHostCtrl/Types/EventWaitArguments.cs | 13 + .../NvHostCtrl/Types/GetConfigurationArguments.cs | 34 + .../NvDrvServices/NvHostCtrl/Types/NvHostEvent.cs | 185 + .../NvHostCtrl/Types/NvHostEventState.cs | 12 + .../NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs | 199 + .../NvHostCtrl/Types/SyncptWaitArguments.cs | 12 + .../NvHostCtrl/Types/SyncptWaitExArguments.cs | 11 + .../NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs | 239 + .../Types/GetActiveSlotMaskArguments.cs | 11 + .../Types/GetCharacteristicsArguments.cs | 59 + .../NvHostCtrlGpu/Types/GetGpuTimeArguments.cs | 11 + .../NvHostCtrlGpu/Types/GetTpcMasksArguments.cs | 15 + .../NvHostCtrlGpu/Types/ZbcSetTableArguments.cs | 49 + .../Types/ZcullGetCtxSizeArguments.cs | 10 + .../NvHostCtrlGpu/Types/ZcullGetInfoArguments.cs | 19 + .../NvHostDbgGpu/NvHostDbgGpuDeviceFile.cs | 11 + .../NvHostProfGpu/NvHostProfGpuDeviceFile.cs | 11 + .../Services/Nv/NvDrvServices/NvInternalResult.cs | 32 + .../Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs | 272 + .../Nv/NvDrvServices/NvMap/Types/NvMapAlloc.cs | 15 + .../Nv/NvDrvServices/NvMap/Types/NvMapCreate.cs | 11 + .../Nv/NvDrvServices/NvMap/Types/NvMapFree.cs | 14 + .../Nv/NvDrvServices/NvMap/Types/NvMapFromId.cs | 11 + .../Nv/NvDrvServices/NvMap/Types/NvMapGetId.cs | 11 + .../Nv/NvDrvServices/NvMap/Types/NvMapHandle.cs | 40 + .../NvDrvServices/NvMap/Types/NvMapHandleParam.cs | 12 + .../NvDrvServices/NvMap/Types/NvMapIdDictionary.cs | 61 + .../Nv/NvDrvServices/NvMap/Types/NvMapParam.cs | 12 + src/Ryujinx.HLE/HOS/Services/Nv/NvIoctl.cs | 45 + .../HOS/Services/Nv/NvMemoryAllocator.cs | 310 ++ src/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs | 41 + .../Nv/Types/NvIoctlNotImplementedException.cs | 55 + .../Types/NvQueryEventNotImplementedException.cs | 51 + src/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs | 30 + src/Ryujinx.HLE/HOS/Services/Nv/Types/NvStatus.cs | 15 + .../Services/Olsc/IOlscServiceForApplication.cs | 90 + .../Services/Olsc/IOlscServiceForSystemService.cs | 8 + src/Ryujinx.HLE/HOS/Services/Olsc/ResultCode.cs | 13 + .../HOS/Services/Ovln/IReceiverService.cs | 8 + .../HOS/Services/Ovln/ISenderService.cs | 8 + src/Ryujinx.HLE/HOS/Services/Pcie/ILogManager.cs | 8 + src/Ryujinx.HLE/HOS/Services/Pcie/IManager.cs | 8 + .../Pctl/IParentalControlServiceFactory.cs | 40 + .../IParentalControlService.cs | 259 + src/Ryujinx.HLE/HOS/Services/Pctl/ResultCode.cs | 16 + .../Services/Pcv/Bpc/IBoardPowerControlManager.cs | 8 + .../HOS/Services/Pcv/Bpc/IRtcManager.cs | 32 + .../Pcv/Clkrst/ClkrstManager/IClkrstSession.cs | 62 + .../HOS/Services/Pcv/Clkrst/IArbitrationManager.cs | 8 + .../HOS/Services/Pcv/Clkrst/IClkrstManager.cs | 57 + src/Ryujinx.HLE/HOS/Services/Pcv/IPcvService.cs | 8 + src/Ryujinx.HLE/HOS/Services/Pcv/ResultCode.cs | 12 + .../HOS/Services/Pcv/Rgltr/IRegulatorManager.cs | 8 + .../HOS/Services/Pcv/Rtc/IRtcManager.cs | 8 + .../HOS/Services/Pcv/Types/DeviceCode.cs | 94 + .../HOS/Services/Pm/IBootModeInterface.cs | 8 + .../HOS/Services/Pm/IDebugMonitorInterface.cs | 49 + .../HOS/Services/Pm/IInformationInterface.cs | 27 + src/Ryujinx.HLE/HOS/Services/Pm/IShellInterface.cs | 21 + src/Ryujinx.HLE/HOS/Services/Pm/ResultCode.cs | 17 + src/Ryujinx.HLE/HOS/Services/Psc/IPmControl.cs | 8 + src/Ryujinx.HLE/HOS/Services/Psc/IPmService.cs | 8 + src/Ryujinx.HLE/HOS/Services/Psc/IPmUnknown.cs | 8 + src/Ryujinx.HLE/HOS/Services/Ptm/Fan/IManager.cs | 8 + src/Ryujinx.HLE/HOS/Services/Ptm/Fgm/IDebugger.cs | 8 + src/Ryujinx.HLE/HOS/Services/Ptm/Fgm/ISession.cs | 10 + src/Ryujinx.HLE/HOS/Services/Ptm/Pcm/IManager.cs | 8 + src/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmServer.cs | 45 + .../HOS/Services/Ptm/Psm/IPsmSession.cs | 88 + .../HOS/Services/Ptm/Psm/Types/ChargerType.cs | 9 + src/Ryujinx.HLE/HOS/Services/Ptm/Tc/IManager.cs | 8 + .../HOS/Services/Ptm/Ts/IMeasurementServer.cs | 39 + .../HOS/Services/Ptm/Ts/Types/Location.cs | 8 + src/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs | 602 +++ src/Ryujinx.HLE/HOS/Services/Ro/ResultCode.cs | 27 + .../HOS/Services/Ro/Types/NRRCertification.cs | 15 + src/Ryujinx.HLE/HOS/Services/Ro/Types/NroInfo.cs | 35 + src/Ryujinx.HLE/HOS/Services/Ro/Types/NrrHeader.cs | 22 + src/Ryujinx.HLE/HOS/Services/Ro/Types/NrrInfo.cs | 18 + .../HOS/Services/Sdb/Avm/IAvmService.cs | 8 + .../HOS/Services/Sdb/Pdm/INotifyService.cs | 8 + .../HOS/Services/Sdb/Pdm/IQueryService.cs | 24 + .../Pdm/QueryService/QueryPlayStatisticsManager.cs | 84 + .../Types/ApplicationPlayStatistics.cs | 12 + .../QueryService/Types/PlayLogQueryCapability.cs | 9 + src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/ResultCode.cs | 15 + .../HOS/Services/Sdb/Pl/ISharedFontManager.cs | 140 + .../HOS/Services/Sdb/Pl/SharedFontManager.cs | 183 + .../HOS/Services/Sdb/Pl/Types/SharedFontType.cs | 13 + src/Ryujinx.HLE/HOS/Services/ServerBase.cs | 423 ++ src/Ryujinx.HLE/HOS/Services/ServiceAttributes.cs | 17 + .../Services/Settings/IFactorySettingsServer.cs | 8 + .../Settings/IFirmwareDebugSettingsServer.cs | 8 + .../HOS/Services/Settings/ISettingsServer.cs | 247 + .../HOS/Services/Settings/ISystemSettingsServer.cs | 348 ++ .../HOS/Services/Settings/KeyCodeMaps.cs | 4849 ++++++++++++++++++ .../HOS/Services/Settings/NxSettings.cs | 1712 +++++++ .../HOS/Services/Settings/ResultCode.cs | 126 + .../HOS/Services/Settings/Types/PlatformRegion.cs | 8 + .../HOS/Services/Sm/IManagerInterface.cs | 8 + src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs | 261 + src/Ryujinx.HLE/HOS/Services/Sm/ResultCode.cs | 15 + src/Ryujinx.HLE/HOS/Services/Sm/SmRegistry.cs | 49 + .../HOS/Services/Sockets/Bsd/BsdContext.cs | 184 + .../HOS/Services/Sockets/Bsd/IClient.cs | 1121 ++++ .../HOS/Services/Sockets/Bsd/IFileDescriptor.cs | 15 + .../HOS/Services/Sockets/Bsd/ISocket.cs | 53 + .../Sockets/Bsd/Impl/EventFileDescriptor.cs | 153 + .../Bsd/Impl/EventFileDescriptorPollManager.cs | 122 + .../HOS/Services/Sockets/Bsd/Impl/ManagedSocket.cs | 530 ++ .../Sockets/Bsd/Impl/ManagedSocketPollManager.cs | 177 + .../HOS/Services/Sockets/Bsd/Impl/WSAError.cs | 134 + .../HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs | 225 + .../HOS/Services/Sockets/Bsd/ServerInterface.cs | 8 + .../Services/Sockets/Bsd/Types/BsdAddressFamily.cs | 11 + .../HOS/Services/Sockets/Bsd/Types/BsdIoctl.cs | 7 + .../HOS/Services/Sockets/Bsd/Types/BsdMMsgHdr.cs | 56 + .../HOS/Services/Sockets/Bsd/Types/BsdMsgHdr.cs | 212 + .../HOS/Services/Sockets/Bsd/Types/BsdSockAddr.cs | 39 + .../Sockets/Bsd/Types/BsdSocketCreationFlags.cs | 14 + .../Services/Sockets/Bsd/Types/BsdSocketFlags.cs | 22 + .../Services/Sockets/Bsd/Types/BsdSocketOption.cs | 119 + .../Sockets/Bsd/Types/BsdSocketShutdownFlags.cs | 9 + .../Services/Sockets/Bsd/Types/BsdSocketType.cs | 13 + .../HOS/Services/Sockets/Bsd/Types/EventFdFlags.cs | 12 + .../HOS/Services/Sockets/Bsd/Types/IPollManager.cs | 13 + .../HOS/Services/Sockets/Bsd/Types/LinuxError.cs | 155 + .../HOS/Services/Sockets/Bsd/Types/PollEvent.cs | 14 + .../Services/Sockets/Bsd/Types/PollEventData.cs | 11 + .../Sockets/Bsd/Types/PollEventTypeMask.cs | 15 + .../HOS/Services/Sockets/Bsd/Types/TimeVal.cs | 8 + .../HOS/Services/Sockets/Ethc/IEthInterface.cs | 8 + .../Services/Sockets/Ethc/IEthInterfaceGroup.cs | 8 + .../HOS/Services/Sockets/Nsd/IManager.cs | 402 ++ .../Services/Sockets/Nsd/Manager/FqdnResolver.cs | 97 + .../HOS/Services/Sockets/Nsd/ResultCode.cs | 19 + .../Nsd/Types/ApplicationServerEnvironmentType.cs | 11 + .../HOS/Services/Sockets/Nsd/Types/NsdSettings.cs | 9 + .../HOS/Services/Sockets/Sfdnsres/IResolver.cs | 686 +++ .../Sockets/Sfdnsres/Proxy/DnsBlacklist.cs | 44 + .../Sockets/Sfdnsres/Proxy/DnsMitmResolver.cs | 106 + .../Services/Sockets/Sfdnsres/Types/AddrInfo4.cs | 51 + .../Sockets/Sfdnsres/Types/AddrInfoSerialized.cs | 143 + .../Sfdnsres/Types/AddrInfoSerializedHeader.cs | 57 + .../Services/Sockets/Sfdnsres/Types/GaiError.cs | 22 + .../Services/Sockets/Sfdnsres/Types/NetDBError.cs | 13 + .../Sockets/Sfdnsres/Types/SfdnsresContants.cs | 7 + .../HOS/Services/Spl/IGeneralInterface.cs | 126 + .../HOS/Services/Spl/IRandomInterface.cs | 38 + src/Ryujinx.HLE/HOS/Services/Spl/ResultCode.cs | 12 + .../HOS/Services/Spl/Types/ConfigItem.cs | 24 + src/Ryujinx.HLE/HOS/Services/Spl/Types/DramId.cs | 35 + .../HOS/Services/Spl/Types/HardwareState.cs | 8 + .../HOS/Services/Spl/Types/HardwareType.cs | 12 + .../HOS/Services/Spl/Types/SmcResult.cs | 20 + .../HOS/Services/Srepo/ISrepoService.cs | 9 + .../HOS/Services/Ssl/BuiltInCertificateManager.cs | 246 + src/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs | 125 + src/Ryujinx.HLE/HOS/Services/Ssl/ResultCode.cs | 20 + .../HOS/Services/Ssl/SslService/ISslConnection.cs | 519 ++ .../Services/Ssl/SslService/ISslConnectionBase.cs | 24 + .../HOS/Services/Ssl/SslService/ISslContext.cs | 83 + .../Ssl/SslService/SslManagedSocketConnection.cs | 251 + .../Services/Ssl/Types/BuiltInCertificateInfo.cs | 10 + .../HOS/Services/Ssl/Types/CaCertificateId.cs | 68 + .../HOS/Services/Ssl/Types/CertificateFormat.cs | 8 + src/Ryujinx.HLE/HOS/Services/Ssl/Types/IoMode.cs | 8 + .../HOS/Services/Ssl/Types/OptionType.cs | 10 + .../HOS/Services/Ssl/Types/SessionCacheMode.cs | 9 + .../HOS/Services/Ssl/Types/SslVersion.cs | 16 + .../HOS/Services/Ssl/Types/TrustedCertStatus.cs | 12 + .../HOS/Services/Ssl/Types/VerifyOption.cs | 15 + .../Services/SurfaceFlinger/BufferItemConsumer.cs | 95 + .../HOS/Services/SurfaceFlinger/BufferQueue.cs | 15 + .../Services/SurfaceFlinger/BufferQueueConsumer.cs | 420 ++ .../HOS/Services/SurfaceFlinger/BufferQueueCore.cs | 341 ++ .../Services/SurfaceFlinger/BufferQueueProducer.cs | 871 ++++ .../HOS/Services/SurfaceFlinger/BufferSlot.cs | 29 + .../HOS/Services/SurfaceFlinger/BufferSlotArray.cs | 28 + .../HOS/Services/SurfaceFlinger/ConsumerBase.cs | 175 + .../SurfaceFlinger/HOSBinderDriverServer.cs | 109 + .../HOS/Services/SurfaceFlinger/IBinder.cs | 41 + .../Services/SurfaceFlinger/IConsumerListener.cs | 9 + .../HOS/Services/SurfaceFlinger/IFlattenable.cs | 13 + .../SurfaceFlinger/IGraphicBufferProducer.cs | 304 ++ .../Services/SurfaceFlinger/IHOSBinderDriver.cs | 109 + .../Services/SurfaceFlinger/IProducerListener.cs | 7 + .../HOS/Services/SurfaceFlinger/LayerState.cs | 10 + .../HOS/Services/SurfaceFlinger/NativeWindowApi.cs | 11 + .../SurfaceFlinger/NativeWindowAttribute.cs | 13 + .../SurfaceFlinger/NativeWindowScalingMode.cs | 11 + .../SurfaceFlinger/NativeWindowTransform.cs | 18 + .../HOS/Services/SurfaceFlinger/Parcel.cs | 221 + .../HOS/Services/SurfaceFlinger/ParcelHeader.cs | 10 + .../HOS/Services/SurfaceFlinger/PixelFormat.cs | 14 + .../HOS/Services/SurfaceFlinger/Status.cs | 22 + .../HOS/Services/SurfaceFlinger/SurfaceFlinger.cs | 548 ++ .../Services/SurfaceFlinger/Types/AndroidFence.cs | 104 + .../SurfaceFlinger/Types/AndroidStrongPointer.cs | 38 + .../Services/SurfaceFlinger/Types/BufferInfo.cs | 14 + .../Services/SurfaceFlinger/Types/BufferItem.cs | 62 + .../Services/SurfaceFlinger/Types/BufferState.cs | 10 + .../Types/Color/ColorBytePerPixel.cs | 17 + .../SurfaceFlinger/Types/Color/ColorComponent.cs | 42 + .../SurfaceFlinger/Types/Color/ColorDataType.cs | 9 + .../SurfaceFlinger/Types/Color/ColorFormat.cs | 235 + .../SurfaceFlinger/Types/Color/ColorShift.cs | 10 + .../SurfaceFlinger/Types/Color/ColorSpace.cs | 33 + .../SurfaceFlinger/Types/Color/ColorSwizzle.cs | 31 + .../Services/SurfaceFlinger/Types/GraphicBuffer.cs | 74 + .../SurfaceFlinger/Types/GraphicBufferHeader.cs | 21 + .../SurfaceFlinger/Types/NvGraphicBuffer.cs | 41 + .../SurfaceFlinger/Types/NvGraphicBufferSurface.cs | 44 + .../Types/NvGraphicBufferSurfaceArray.cs | 41 + .../HOS/Services/SurfaceFlinger/Types/Rect.cs | 71 + .../EphemeralNetworkSystemClockContextWriter.cs | 10 + .../Time/Clock/EphemeralNetworkSystemClockCore.cs | 7 + .../Time/Clock/LocalSystemClockContextWriter.cs | 19 + .../Time/Clock/NetworkSystemClockContextWriter.cs | 19 + .../Time/Clock/StandardLocalSystemClockCore.cs | 7 + .../Time/Clock/StandardNetworkSystemClockCore.cs | 36 + .../Services/Time/Clock/StandardSteadyClockCore.cs | 72 + .../Time/Clock/StandardUserSystemClockCore.cs | 108 + .../HOS/Services/Time/Clock/SteadyClockCore.cs | 98 + .../Time/Clock/SystemClockContextUpdateCallback.cs | 71 + .../HOS/Services/Time/Clock/SystemClockCore.cs | 144 + .../Time/Clock/TickBasedSteadyClockCore.cs | 24 + .../HOS/Services/Time/Clock/Types/ClockSnapshot.cs | 50 + .../Time/Clock/Types/SteadyClockTimePoint.cs | 43 + .../Time/Clock/Types/SystemClockContext.cs | 11 + .../HOS/Services/Time/Clock/Types/TimeSpanType.cs | 50 + src/Ryujinx.HLE/HOS/Services/Time/IAlarmService.cs | 8 + .../HOS/Services/Time/IPowerStateRequestHandler.cs | 8 + .../HOS/Services/Time/IStaticServiceForGlue.cs | 184 + .../HOS/Services/Time/IStaticServiceForPsc.cs | 433 ++ .../HOS/Services/Time/ITimeServiceManager.cs | 231 + src/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs | 24 + .../Services/Time/StaticService/ISteadyClock.cs | 155 + .../Services/Time/StaticService/ISystemClock.cs | 131 + .../Time/StaticService/ITimeZoneServiceForGlue.cs | 142 + .../Time/StaticService/ITimeZoneServiceForPsc.cs | 303 ++ src/Ryujinx.HLE/HOS/Services/Time/TimeManager.cs | 182 + .../HOS/Services/Time/TimeSharedMemory.cs | 114 + .../HOS/Services/Time/TimeZone/TimeZone.cs | 1703 +++++++ .../Time/TimeZone/TimeZoneContentManager.cs | 304 ++ .../HOS/Services/Time/TimeZone/TimeZoneManager.cs | 261 + .../Time/TimeZone/Types/CalendarAdditionalInfo.cs | 21 + .../Services/Time/TimeZone/Types/CalendarInfo.cs | 11 + .../Services/Time/TimeZone/Types/CalendarTime.cs | 15 + .../Services/Time/TimeZone/Types/TimeTypeInfo.cs | 28 + .../Services/Time/TimeZone/Types/TimeZoneRule.cs | 56 + .../HOS/Services/Time/TimeZone/Types/TzifHeader.cs | 19 + .../HOS/Services/Time/Types/SteadyClockContext.cs | 12 + .../HOS/Services/Time/Types/TimePermissions.cs | 22 + .../HOS/Services/Usb/IClientRootSession.cs | 9 + src/Ryujinx.HLE/HOS/Services/Usb/IDsService.cs | 8 + .../HOS/Services/Usb/IPdCradleManager.cs | 8 + src/Ryujinx.HLE/HOS/Services/Usb/IPdManager.cs | 8 + src/Ryujinx.HLE/HOS/Services/Usb/IPmService.cs | 8 + src/Ryujinx.HLE/HOS/Services/Usb/IUnknown1.cs | 8 + src/Ryujinx.HLE/HOS/Services/Usb/IUnknown2.cs | 8 + .../HOS/Services/Vi/IApplicationRootService.cs | 27 + .../HOS/Services/Vi/IManagerRootService.cs | 28 + .../HOS/Services/Vi/ISystemRootService.cs | 28 + src/Ryujinx.HLE/HOS/Services/Vi/ResultCode.cs | 17 + .../AndroidSurfaceComposerClient.cs | 19 + .../IManagerDisplayService.cs | 80 + .../ISystemDisplayService.cs | 59 + .../Types/DestinationScalingMode.cs | 11 + .../ApplicationDisplayService/Types/DisplayInfo.cs | 16 + .../Types/SourceScalingMode.cs | 11 + .../Vi/RootService/IApplicationDisplayService.cs | 487 ++ .../HOS/Services/Vi/Types/ViServiceType.cs | 9 + src/Ryujinx.HLE/HOS/Services/Wlan/IInfraManager.cs | 8 + .../HOS/Services/Wlan/ILocalGetActionFrame.cs | 8 + .../HOS/Services/Wlan/ILocalGetFrame.cs | 8 + src/Ryujinx.HLE/HOS/Services/Wlan/ILocalManager.cs | 8 + .../HOS/Services/Wlan/ISocketGetFrame.cs | 8 + .../HOS/Services/Wlan/ISocketManager.cs | 8 + src/Ryujinx.HLE/HOS/Services/Wlan/IUnknown1.cs | 8 + src/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs | 42 + src/Ryujinx.HLE/HOS/SystemState/ColorSet.cs | 8 + src/Ryujinx.HLE/HOS/SystemState/KeyboardLayout.cs | 25 + src/Ryujinx.HLE/HOS/SystemState/RegionCode.cs | 17 + src/Ryujinx.HLE/HOS/SystemState/SystemLanguage.cs | 24 + src/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs | 90 + src/Ryujinx.HLE/HOS/SystemState/TitleLanguage.cs | 22 + src/Ryujinx.HLE/HOS/Tamper/AtmosphereCompiler.cs | 152 + src/Ryujinx.HLE/HOS/Tamper/AtmosphereProgram.cs | 33 + .../HOS/Tamper/CodeEmitters/Arithmetic.cs | 105 + .../Tamper/CodeEmitters/BeginConditionalBlock.cs | 14 + .../HOS/Tamper/CodeEmitters/DebugLog.cs | 87 + .../HOS/Tamper/CodeEmitters/EndConditionalBlock.cs | 91 + .../HOS/Tamper/CodeEmitters/KeyPressConditional.cs | 26 + .../HOS/Tamper/CodeEmitters/LegacyArithmetic.cs | 57 + .../CodeEmitters/LoadRegisterWithConstant.cs | 28 + .../Tamper/CodeEmitters/LoadRegisterWithMemory.cs | 58 + .../HOS/Tamper/CodeEmitters/MemoryConditional.cs | 45 + .../HOS/Tamper/CodeEmitters/PauseProcess.cs | 17 + .../CodeEmitters/ReadOrWriteStaticRegister.cs | 47 + .../HOS/Tamper/CodeEmitters/RegisterConditional.cs | 106 + .../HOS/Tamper/CodeEmitters/ResumeProcess.cs | 17 + .../Tamper/CodeEmitters/SaveOrRestoreRegister.cs | 65 + .../CodeEmitters/SaveOrRestoreRegisterWithMask.cs | 33 + .../HOS/Tamper/CodeEmitters/StartEndLoop.cs | 72 + .../Tamper/CodeEmitters/StoreConstantToAddress.cs | 41 + .../Tamper/CodeEmitters/StoreConstantToMemory.cs | 71 + .../Tamper/CodeEmitters/StoreRegisterToMemory.cs | 99 + src/Ryujinx.HLE/HOS/Tamper/CodeType.cs | 110 + src/Ryujinx.HLE/HOS/Tamper/Comparison.cs | 15 + src/Ryujinx.HLE/HOS/Tamper/CompilationContext.cs | 75 + src/Ryujinx.HLE/HOS/Tamper/Conditions/CondEQ.cs | 21 + src/Ryujinx.HLE/HOS/Tamper/Conditions/CondGE.cs | 21 + src/Ryujinx.HLE/HOS/Tamper/Conditions/CondGT.cs | 21 + src/Ryujinx.HLE/HOS/Tamper/Conditions/CondLE.cs | 21 + src/Ryujinx.HLE/HOS/Tamper/Conditions/CondLT.cs | 21 + src/Ryujinx.HLE/HOS/Tamper/Conditions/CondNE.cs | 21 + .../HOS/Tamper/Conditions/ICondition.cs | 7 + src/Ryujinx.HLE/HOS/Tamper/Conditions/InputMask.cs | 19 + src/Ryujinx.HLE/HOS/Tamper/ITamperProgram.cs | 13 + src/Ryujinx.HLE/HOS/Tamper/ITamperedProcess.cs | 16 + src/Ryujinx.HLE/HOS/Tamper/InstructionHelper.cs | 133 + src/Ryujinx.HLE/HOS/Tamper/MemoryHelper.cs | 95 + src/Ryujinx.HLE/HOS/Tamper/MemoryRegion.cs | 29 + src/Ryujinx.HLE/HOS/Tamper/OperationBlock.cs | 17 + src/Ryujinx.HLE/HOS/Tamper/Operations/Block.cs | 27 + src/Ryujinx.HLE/HOS/Tamper/Operations/ForBlock.cs | 41 + src/Ryujinx.HLE/HOS/Tamper/Operations/IOperand.cs | 8 + .../HOS/Tamper/Operations/IOperation.cs | 7 + src/Ryujinx.HLE/HOS/Tamper/Operations/IfBlock.cs | 34 + src/Ryujinx.HLE/HOS/Tamper/Operations/OpAdd.cs | 21 + src/Ryujinx.HLE/HOS/Tamper/Operations/OpAnd.cs | 21 + src/Ryujinx.HLE/HOS/Tamper/Operations/OpLog.cs | 21 + src/Ryujinx.HLE/HOS/Tamper/Operations/OpLsh.cs | 21 + src/Ryujinx.HLE/HOS/Tamper/Operations/OpMov.cs | 19 + src/Ryujinx.HLE/HOS/Tamper/Operations/OpMul.cs | 21 + src/Ryujinx.HLE/HOS/Tamper/Operations/OpNot.cs | 19 + src/Ryujinx.HLE/HOS/Tamper/Operations/OpOr.cs | 21 + .../HOS/Tamper/Operations/OpProcCtrl.cs | 26 + src/Ryujinx.HLE/HOS/Tamper/Operations/OpRsh.cs | 21 + src/Ryujinx.HLE/HOS/Tamper/Operations/OpSub.cs | 21 + src/Ryujinx.HLE/HOS/Tamper/Operations/OpXor.cs | 21 + src/Ryujinx.HLE/HOS/Tamper/Parameter.cs | 12 + src/Ryujinx.HLE/HOS/Tamper/Pointer.cs | 32 + src/Ryujinx.HLE/HOS/Tamper/Register.cs | 28 + src/Ryujinx.HLE/HOS/Tamper/TamperedKProcess.cs | 68 + src/Ryujinx.HLE/HOS/Tamper/Value.cs | 24 + src/Ryujinx.HLE/HOS/TamperMachine.cs | 186 + src/Ryujinx.HLE/HOS/UserChannelPersistence.cs | 60 + src/Ryujinx.HLE/Homebrew.npdm | Bin 0 -> 972 bytes src/Ryujinx.HLE/Loaders/Elf/ElfDynamic.cs | 15 + src/Ryujinx.HLE/Loaders/Elf/ElfDynamicTag.cs | 75 + src/Ryujinx.HLE/Loaders/Elf/ElfSymbol.cs | 35 + src/Ryujinx.HLE/Loaders/Elf/ElfSymbol32.cs | 14 + src/Ryujinx.HLE/Loaders/Elf/ElfSymbol64.cs | 14 + src/Ryujinx.HLE/Loaders/Elf/ElfSymbolBinding.cs | 9 + src/Ryujinx.HLE/Loaders/Elf/ElfSymbolType.cs | 13 + src/Ryujinx.HLE/Loaders/Elf/ElfSymbolVisibility.cs | 10 + src/Ryujinx.HLE/Loaders/Executables/IExecutable.cs | 18 + .../Loaders/Executables/KipExecutable.cs | 86 + .../Loaders/Executables/NroExecutable.cs | 38 + .../Loaders/Executables/NsoExecutable.cs | 123 + src/Ryujinx.HLE/Loaders/Mods/IPSPatcher.cs | 117 + src/Ryujinx.HLE/Loaders/Mods/IPSwitchPatcher.cs | 275 + src/Ryujinx.HLE/Loaders/Mods/MemPatch.cs | 96 + src/Ryujinx.HLE/Loaders/Npdm/ACI0.cs | 53 + src/Ryujinx.HLE/Loaders/Npdm/ACID.cs | 61 + src/Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs | 28 + src/Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs | 37 + .../Loaders/Npdm/KernelAccessControl.cs | 23 + src/Ryujinx.HLE/Loaders/Npdm/Npdm.cs | 72 + .../Loaders/Npdm/ServiceAccessControl.cs | 42 + .../Processes/Extensions/FileSystemExtensions.cs | 133 + .../Extensions/LocalFileSystemExtensions.cs | 39 + .../Processes/Extensions/MetaLoaderExtensions.cs | 61 + .../Loaders/Processes/Extensions/NcaExtensions.cs | 175 + .../Extensions/PartitionFileSystemExtensions.cs | 180 + src/Ryujinx.HLE/Loaders/Processes/ProcessConst.cs | 33 + src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs | 244 + .../Loaders/Processes/ProcessLoaderHelper.cs | 467 ++ src/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs | 94 + src/Ryujinx.HLE/MemoryConfiguration.cs | 62 + src/Ryujinx.HLE/PerformanceStatistics.cs | 167 + src/Ryujinx.HLE/Ryujinx.HLE.csproj | 55 + src/Ryujinx.HLE/Switch.cs | 156 + src/Ryujinx.HLE/Ui/DynamicTextChangedHandler.cs | 4 + src/Ryujinx.HLE/Ui/IDynamicTextInputHandler.cs | 16 + src/Ryujinx.HLE/Ui/IHostUiHandler.cs | 51 + src/Ryujinx.HLE/Ui/IHostUiTheme.cs | 13 + src/Ryujinx.HLE/Ui/Input/NpadButtonHandler.cs | 6 + src/Ryujinx.HLE/Ui/Input/NpadReader.cs | 136 + src/Ryujinx.HLE/Ui/KeyPressedHandler.cs | 6 + src/Ryujinx.HLE/Ui/KeyReleasedHandler.cs | 6 + src/Ryujinx.HLE/Ui/RenderingSurfaceInfo.cs | 35 + src/Ryujinx.HLE/Ui/ThemeColor.cs | 18 + src/Ryujinx.HLE/Utilities/StringUtils.cs | 159 + .../HeadlessDynamicTextInputHandler.cs | 51 + src/Ryujinx.Headless.SDL2/HeadlessHostUiTheme.cs | 17 + src/Ryujinx.Headless.SDL2/HideCursor.cs | 9 + src/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs | 169 + src/Ryujinx.Headless.SDL2/Options.cs | 209 + src/Ryujinx.Headless.SDL2/Program.cs | 704 +++ .../Ryujinx.Headless.SDL2.csproj | 66 + src/Ryujinx.Headless.SDL2/Ryujinx.bmp | Bin 0 -> 9354 bytes src/Ryujinx.Headless.SDL2/SDL2Mouse.cs | 90 + src/Ryujinx.Headless.SDL2/SDL2MouseDriver.cs | 164 + .../StatusUpdatedEventArgs.cs | 24 + src/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs | 104 + src/Ryujinx.Headless.SDL2/WindowBase.cs | 499 ++ src/Ryujinx.Horizon.Common/ISyscallApi.cs | 33 + src/Ryujinx.Horizon.Common/IThreadContext.cs | 11 + .../InvalidResultException.cs | 23 + src/Ryujinx.Horizon.Common/KernelResult.cs | 39 + src/Ryujinx.Horizon.Common/OnScopeExit.cs | 19 + src/Ryujinx.Horizon.Common/Result.cs | 118 + src/Ryujinx.Horizon.Common/ResultNames.cs | 1701 +++++++ .../Ryujinx.Horizon.Common.csproj | 11 + .../ThreadTerminatedException.cs | 19 + src/Ryujinx.Horizon.Generators/CodeGenerator.cs | 63 + .../Hipc/CommandArgType.cs | 18 + .../Hipc/CommandInterface.cs | 17 + .../Hipc/HipcGenerator.cs | 758 +++ .../Hipc/HipcSyntaxReceiver.cs | 58 + .../Ryujinx.Horizon.Generators.csproj | 16 + .../CodeGenerator.cs | 58 + .../Kernel/SyscallGenerator.cs | 520 ++ .../Kernel/SyscallSyntaxReceiver.cs | 53 + .../Ryujinx.Horizon.Kernel.Generators.csproj | 16 + src/Ryujinx.Horizon/HeapAllocator.cs | 143 + src/Ryujinx.Horizon/HorizonOptions.cs | 14 + src/Ryujinx.Horizon/HorizonStatic.cs | 44 + src/Ryujinx.Horizon/IService.cs | 7 + src/Ryujinx.Horizon/LogManager/Ipc/LmLogger.cs | 176 + src/Ryujinx.Horizon/LogManager/Ipc/LogService.cs | 20 + src/Ryujinx.Horizon/LogManager/LmIpcServer.cs | 43 + src/Ryujinx.Horizon/LogManager/LmMain.cs | 17 + src/Ryujinx.Horizon/LogManager/Types/LogPacket.cs | 72 + src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs | 225 + src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs | 49 + src/Ryujinx.Horizon/Prepo/PrepoMain.cs | 17 + src/Ryujinx.Horizon/Prepo/PrepoResult.cs | 15 + src/Ryujinx.Horizon/Prepo/PrepoServerManager.cs | 30 + src/Ryujinx.Horizon/Prepo/Types/PrepoPortIndex.cs | 12 + .../Prepo/Types/PrepoServicePermissionLevel.cs | 11 + src/Ryujinx.Horizon/Ryujinx.Horizon.csproj | 18 + src/Ryujinx.Horizon/Sdk/Account/Uid.cs | 62 + src/Ryujinx.Horizon/Sdk/DebugUtil.cs | 12 + src/Ryujinx.Horizon/Sdk/Diag/LogSeverity.cs | 11 + src/Ryujinx.Horizon/Sdk/Lm/ILmLogger.cs | 12 + src/Ryujinx.Horizon/Sdk/Lm/ILogService.cs | 11 + src/Ryujinx.Horizon/Sdk/Lm/LogDataChunkKey.cs | 19 + src/Ryujinx.Horizon/Sdk/Lm/LogDestination.cs | 14 + src/Ryujinx.Horizon/Sdk/Lm/LogPacketFlags.cs | 12 + src/Ryujinx.Horizon/Sdk/Lm/LogPacketHeader.cs | 15 + src/Ryujinx.Horizon/Sdk/Ncm/ApplicationId.cs | 52 + src/Ryujinx.Horizon/Sdk/OsTypes/Event.cs | 61 + src/Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs | 8 + src/Ryujinx.Horizon/Sdk/OsTypes/EventType.cs | 15 + .../Sdk/OsTypes/Impl/InterProcessEvent.cs | 89 + .../Sdk/OsTypes/Impl/InterProcessEventImpl.cs | 136 + .../Sdk/OsTypes/Impl/MultiWaitImpl.cs | 250 + .../Sdk/OsTypes/InitializationState.cs | 8 + .../Sdk/OsTypes/InterProcessEventType.cs | 27 + src/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs | 43 + src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs | 16 + .../Sdk/OsTypes/MultiWaitHolderBase.cs | 39 + .../Sdk/OsTypes/MultiWaitHolderOfEvent.cs | 45 + .../Sdk/OsTypes/MultiWaitHolderOfHandle.cs | 14 + src/Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs | 130 + src/Ryujinx.Horizon/Sdk/OsTypes/OsMultiWait.cs | 10 + src/Ryujinx.Horizon/Sdk/OsTypes/OsProcessHandle.cs | 33 + src/Ryujinx.Horizon/Sdk/OsTypes/OsResult.cs | 11 + src/Ryujinx.Horizon/Sdk/OsTypes/OsSystemEvent.cs | 85 + src/Ryujinx.Horizon/Sdk/OsTypes/OsThreadManager.cs | 10 + src/Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs | 17 + src/Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs | 9 + src/Ryujinx.Horizon/Sdk/Prepo/IPrepoService.cs | 20 + src/Ryujinx.Horizon/Sdk/ServiceUtil.cs | 39 + .../Sdk/Sf/Cmif/CmifDomainInHeader.cs | 12 + .../Sdk/Sf/Cmif/CmifDomainOutHeader.cs | 12 + .../Sdk/Sf/Cmif/CmifDomainRequestType.cs | 9 + src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs | 10 + src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs | 135 + src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs | 14 + src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs | 14 + .../Sdk/Sf/Cmif/CmifRequestFormat.cs | 24 + src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs | 12 + src/Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs | 14 + .../Sdk/Sf/Cmif/DomainServiceObject.cs | 7 + .../Sf/Cmif/DomainServiceObjectDispatchTable.cs | 75 + .../Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs | 140 + src/Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs | 52 + src/Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs | 11 + src/Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs | 17 + .../Sdk/Sf/Cmif/ScopedInlineContextChange.cs | 19 + .../Sdk/Sf/Cmif/ServerDomainBase.cs | 15 + .../Sdk/Sf/Cmif/ServerDomainManager.cs | 246 + .../Sdk/Sf/Cmif/ServerMessageProcessor.cs | 18 + .../Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs | 30 + .../Sdk/Sf/Cmif/ServiceDispatchContext.cs | 18 + .../Sdk/Sf/Cmif/ServiceDispatchMeta.cs | 12 + .../Sdk/Sf/Cmif/ServiceDispatchTable.cs | 33 + .../Sdk/Sf/Cmif/ServiceDispatchTableBase.cs | 94 + .../Sdk/Sf/Cmif/ServiceObjectHolder.cs | 34 + src/Ryujinx.Horizon/Sdk/Sf/CmifCommandAttribute.cs | 15 + src/Ryujinx.Horizon/Sdk/Sf/CommandArg.cs | 56 + src/Ryujinx.Horizon/Sdk/Sf/CommandArgAttributes.cs | 38 + src/Ryujinx.Horizon/Sdk/Sf/CommandHandler.cs | 52 + src/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs | 69 + src/Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs | 85 + src/Ryujinx.Horizon/Sdk/Sf/Hipc/Header.cs | 65 + .../Sdk/Sf/Hipc/HipcBufferDescriptor.cs | 15 + src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferFlags.cs | 17 + src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferMode.cs | 10 + src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcManager.cs | 115 + src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs | 218 + src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs | 16 + src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMetadata.cs | 16 + .../Sdk/Sf/Hipc/HipcReceiveListEntry.cs | 14 + src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcResult.cs | 19 + .../Sdk/Sf/Hipc/HipcStaticDescriptor.cs | 22 + src/Ryujinx.Horizon/Sdk/Sf/Hipc/ManagerOptions.cs | 20 + src/Ryujinx.Horizon/Sdk/Sf/Hipc/ReceiveResult.cs | 9 + src/Ryujinx.Horizon/Sdk/Sf/Hipc/Server.cs | 36 + .../Sdk/Sf/Hipc/ServerDomainSessionManager.cs | 23 + src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs | 198 + .../Sdk/Sf/Hipc/ServerManagerBase.cs | 314 ++ src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSession.cs | 23 + .../Sdk/Sf/Hipc/ServerSessionManager.cs | 340 ++ src/Ryujinx.Horizon/Sdk/Sf/Hipc/SpecialHeader.cs | 27 + src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs | 428 ++ src/Ryujinx.Horizon/Sdk/Sf/IServiceObject.cs | 9 + .../Sdk/Sf/RawDataOffsetCalculator.cs | 49 + src/Ryujinx.Horizon/Sdk/Sf/SfResult.cs | 27 + src/Ryujinx.Horizon/Sdk/Sm/IManagerService.cs | 8 + src/Ryujinx.Horizon/Sdk/Sm/IUserService.cs | 13 + src/Ryujinx.Horizon/Sdk/Sm/ServiceName.cs | 98 + src/Ryujinx.Horizon/Sdk/Sm/SmApi.cs | 113 + src/Ryujinx.Horizon/ServiceEntry.cs | 27 + src/Ryujinx.Horizon/ServiceTable.cs | 58 + src/Ryujinx.Horizon/Sm/Impl/ServiceInfo.cs | 20 + src/Ryujinx.Horizon/Sm/Impl/ServiceManager.cs | 185 + src/Ryujinx.Horizon/Sm/Ipc/ManagerService.cs | 8 + src/Ryujinx.Horizon/Sm/Ipc/UserService.cs | 66 + src/Ryujinx.Horizon/Sm/SmMain.cs | 34 + src/Ryujinx.Horizon/Sm/SmResult.cs | 19 + src/Ryujinx.Horizon/Sm/SmServerManager.cs | 30 + src/Ryujinx.Horizon/Sm/Types/SmPortIndex.cs | 8 + src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj | 13 + src/Ryujinx.Input.SDL2/SDL2Gamepad.cs | 376 ++ src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs | 148 + src/Ryujinx.Input.SDL2/SDL2Keyboard.cs | 419 ++ src/Ryujinx.Input.SDL2/SDLKeyboardDriver.cs | 54 + .../Assigner/GamepadButtonAssigner.cs | 198 + src/Ryujinx.Input/Assigner/IButtonAssigner.cs | 36 + src/Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs | 50 + src/Ryujinx.Input/GamepadButtonInputId.cs | 57 + src/Ryujinx.Input/GamepadFeaturesFlag.cs | 28 + src/Ryujinx.Input/GamepadStateSnapshot.cs | 70 + src/Ryujinx.Input/HLE/InputManager.cs | 54 + src/Ryujinx.Input/HLE/NpadController.cs | 569 +++ src/Ryujinx.Input/HLE/NpadManager.cs | 320 ++ src/Ryujinx.Input/HLE/TouchScreenManager.cs | 99 + src/Ryujinx.Input/IGamepad.cs | 122 + src/Ryujinx.Input/IGamepadDriver.cs | 37 + src/Ryujinx.Input/IKeyboard.cs | 41 + src/Ryujinx.Input/IMouse.cs | 104 + src/Ryujinx.Input/Key.cs | 142 + src/Ryujinx.Input/KeyboardStateSnapshot.cs | 29 + src/Ryujinx.Input/Motion/CemuHook/Client.cs | 475 ++ .../Motion/CemuHook/Protocol/ControllerData.cs | 47 + .../Motion/CemuHook/Protocol/ControllerInfo.cs | 20 + .../Motion/CemuHook/Protocol/Header.cs | 15 + .../Motion/CemuHook/Protocol/MessageType.cs | 9 + .../Motion/CemuHook/Protocol/SharedResponse.cs | 51 + src/Ryujinx.Input/Motion/MotionInput.cs | 65 + src/Ryujinx.Input/Motion/MotionSensorFilter.cs | 162 + src/Ryujinx.Input/MotionInputId.cs | 25 + src/Ryujinx.Input/MouseButton.cs | 16 + src/Ryujinx.Input/MouseStateSnapshot.cs | 45 + src/Ryujinx.Input/Ryujinx.Input.csproj | 17 + src/Ryujinx.Input/StickInputId.cs | 14 + .../MockVirtualMemoryManager.cs | 109 + .../MultiRegionTrackingTests.cs | 439 ++ .../Ryujinx.Memory.Tests.csproj | 18 + src/Ryujinx.Memory.Tests/Tests.cs | 110 + src/Ryujinx.Memory.Tests/TrackingTests.cs | 509 ++ src/Ryujinx.Memory/AddressSpaceManager.cs | 470 ++ src/Ryujinx.Memory/IRefCounted.cs | 8 + src/Ryujinx.Memory/IVirtualMemoryManager.cs | 205 + src/Ryujinx.Memory/IWritableBlock.cs | 11 + src/Ryujinx.Memory/InvalidAccessHandler.cs | 9 + src/Ryujinx.Memory/InvalidMemoryRegionException.cs | 19 + src/Ryujinx.Memory/MemoryAllocationFlags.cs | 52 + src/Ryujinx.Memory/MemoryBlock.cs | 443 ++ src/Ryujinx.Memory/MemoryConstants.cs | 9 + src/Ryujinx.Memory/MemoryManagement.cs | 206 + src/Ryujinx.Memory/MemoryManagementUnix.cs | 200 + src/Ryujinx.Memory/MemoryManagementWindows.cs | 144 + src/Ryujinx.Memory/MemoryManagerUnixHelper.cs | 167 + src/Ryujinx.Memory/MemoryMapFlags.cs | 23 + src/Ryujinx.Memory/MemoryNotContiguousException.cs | 19 + src/Ryujinx.Memory/MemoryPermission.cs | 51 + src/Ryujinx.Memory/MemoryProtectionException.cs | 23 + src/Ryujinx.Memory/NativeMemoryManager.cs | 42 + src/Ryujinx.Memory/PageTable.cs | 141 + src/Ryujinx.Memory/Range/HostMemoryRange.cs | 71 + src/Ryujinx.Memory/Range/IMultiRangeItem.cs | 9 + src/Ryujinx.Memory/Range/INonOverlappingRange.cs | 16 + src/Ryujinx.Memory/Range/IRange.cs | 31 + src/Ryujinx.Memory/Range/MemoryRange.cs | 61 + src/Ryujinx.Memory/Range/MultiRange.cs | 323 ++ src/Ryujinx.Memory/Range/MultiRangeList.cs | 210 + .../Range/NonOverlappingRangeList.cs | 106 + src/Ryujinx.Memory/Range/RangeList.cs | 483 ++ src/Ryujinx.Memory/Ryujinx.Memory.csproj | 12 + src/Ryujinx.Memory/Tracking/AbstractRegion.cs | 73 + src/Ryujinx.Memory/Tracking/BitMap.cs | 199 + src/Ryujinx.Memory/Tracking/ConcurrentBitmap.cs | 152 + src/Ryujinx.Memory/Tracking/IMultiRegionHandle.cs | 55 + src/Ryujinx.Memory/Tracking/IRegionHandle.cs | 18 + src/Ryujinx.Memory/Tracking/MemoryTracking.cs | 306 ++ src/Ryujinx.Memory/Tracking/MultiRegionHandle.cs | 415 ++ src/Ryujinx.Memory/Tracking/PreciseRegionSignal.cs | 4 + src/Ryujinx.Memory/Tracking/RegionHandle.cs | 464 ++ src/Ryujinx.Memory/Tracking/RegionSignal.cs | 4 + .../Tracking/SmartMultiRegionHandle.cs | 280 + src/Ryujinx.Memory/Tracking/VirtualRegion.cs | 144 + src/Ryujinx.Memory/WindowsShared/MappingTree.cs | 87 + .../WindowsShared/PlaceholderManager.cs | 736 +++ src/Ryujinx.Memory/WindowsShared/WindowsApi.cs | 101 + .../WindowsShared/WindowsApiException.cs | 24 + src/Ryujinx.Memory/WindowsShared/WindowsFlags.cs | 52 + src/Ryujinx.Memory/WritableRegion.cs | 38 + src/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj | 15 + src/Ryujinx.SDL2.Common/SDL2Driver.cs | 204 + src/Ryujinx.ShaderTools/Program.cs | 93 + src/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj | 17 + src/Ryujinx.Tests.Unicorn/IndexedProperty.cs | 28 + src/Ryujinx.Tests.Unicorn/MemoryPermission.cs | 14 + .../Ryujinx.Tests.Unicorn.csproj | 17 + src/Ryujinx.Tests.Unicorn/SimdValue.cs | 112 + src/Ryujinx.Tests.Unicorn/UnicornAArch32.cs | 283 + src/Ryujinx.Tests.Unicorn/UnicornAArch64.cs | 298 ++ src/Ryujinx.Tests/.runsettings | 8 + .../Renderer/AudioRendererConfigurationTests.cs | 15 + .../Audio/Renderer/BehaviourParameterTests.cs | 16 + .../Audio/Renderer/BiquadFilterParameterTests.cs | 15 + .../Audio/Renderer/Common/UpdateDataHeaderTests.cs | 15 + .../Audio/Renderer/Common/VoiceUpdateStateTests.cs | 15 + .../Audio/Renderer/Common/WaveBufferTests.cs | 15 + .../Audio/Renderer/Dsp/ResamplerTests.cs | 93 + .../Audio/Renderer/Dsp/UpsamplerTests.cs | 64 + .../Audio/Renderer/EffectInfoParameterTests.cs | 16 + .../Audio/Renderer/EffectOutStatusTests.cs | 16 + .../Audio/Renderer/MemoryPoolParameterTests.cs | 16 + .../Parameter/BehaviourErrorInfoOutStatusTests.cs | 15 + .../Renderer/Parameter/Effect/AuxParameterTests.cs | 15 + .../Effect/BiquadFilterEffectParameterTests.cs | 15 + .../Parameter/Effect/BufferMixerParameterTests.cs | 15 + .../Parameter/Effect/CompressorParameterTests.cs | 16 + .../Parameter/Effect/DelayParameterTests.cs | 15 + .../Parameter/Effect/LimiterParameterTests.cs | 16 + .../Parameter/Effect/LimiterStatisticsTests.cs | 16 + .../Parameter/Effect/Reverb3dParameterTests.cs | 15 + .../Parameter/Effect/ReverbParameterTests.cs | 15 + .../MixInParameterDirtyOnlyUpdateTests.cs | 15 + .../Audio/Renderer/Parameter/MixParameterTests.cs | 15 + .../Parameter/PerformanceInParameterTests.cs | 15 + .../Parameter/PerformanceOutStatusTests.cs | 15 + .../Parameter/RendererInfoOutStatusTests.cs | 15 + .../Parameter/Sink/CircularBufferParameterTests.cs | 15 + .../Parameter/Sink/DeviceParameterTests.cs | 15 + .../Renderer/Parameter/SinkInParameterTests.cs | 15 + .../Audio/Renderer/Parameter/SinkOutStatusTests.cs | 15 + .../Parameter/SplitterInParamHeaderTests.cs | 15 + .../Audio/Renderer/Server/AddressInfoTests.cs | 35 + .../Audio/Renderer/Server/BehaviourContextTests.cs | 296 ++ .../Audio/Renderer/Server/MemoryPoolStateTests.cs | 62 + .../Audio/Renderer/Server/MixStateTests.cs | 15 + .../Audio/Renderer/Server/PoolMapperTests.cs | 135 + .../Renderer/Server/SplitterDestinationTests.cs | 15 + .../Audio/Renderer/Server/SplitterStateTests.cs | 15 + .../Renderer/Server/VoiceChannelResourceTests.cs | 15 + .../Audio/Renderer/Server/VoiceStateTests.cs | 15 + .../Audio/Renderer/Server/WaveBufferTests.cs | 15 + .../VoiceChannelResourceInParameterTests.cs | 15 + .../Audio/Renderer/VoiceInParameterTests.cs | 15 + .../Audio/Renderer/VoiceOutStatusTests.cs | 15 + src/Ryujinx.Tests/Cpu/Arm64CodeGenCommonTests.cs | 46 + src/Ryujinx.Tests/Cpu/CpuContext.cs | 39 + src/Ryujinx.Tests/Cpu/CpuTest.cs | 578 +++ src/Ryujinx.Tests/Cpu/CpuTest32.cs | 624 +++ src/Ryujinx.Tests/Cpu/CpuTestAlu.cs | 266 + src/Ryujinx.Tests/Cpu/CpuTestAlu32.cs | 209 + src/Ryujinx.Tests/Cpu/CpuTestAluBinary.cs | 307 ++ src/Ryujinx.Tests/Cpu/CpuTestAluBinary32.cs | 95 + src/Ryujinx.Tests/Cpu/CpuTestAluImm.cs | 433 ++ src/Ryujinx.Tests/Cpu/CpuTestAluImm32.cs | 55 + src/Ryujinx.Tests/Cpu/CpuTestAluRs.cs | 895 ++++ src/Ryujinx.Tests/Cpu/CpuTestAluRs32.cs | 82 + src/Ryujinx.Tests/Cpu/CpuTestAluRx.cs | 723 +++ src/Ryujinx.Tests/Cpu/CpuTestBf32.cs | 106 + src/Ryujinx.Tests/Cpu/CpuTestBfm.cs | 130 + src/Ryujinx.Tests/Cpu/CpuTestCcmpImm.cs | 102 + src/Ryujinx.Tests/Cpu/CpuTestCcmpReg.cs | 110 + src/Ryujinx.Tests/Cpu/CpuTestCsel.cs | 205 + src/Ryujinx.Tests/Cpu/CpuTestMisc.cs | 482 ++ src/Ryujinx.Tests/Cpu/CpuTestMisc32.cs | 111 + src/Ryujinx.Tests/Cpu/CpuTestMov.cs | 112 + src/Ryujinx.Tests/Cpu/CpuTestMul.cs | 226 + src/Ryujinx.Tests/Cpu/CpuTestMul32.cs | 137 + src/Ryujinx.Tests/Cpu/CpuTestSimd.cs | 3561 +++++++++++++ src/Ryujinx.Tests/Cpu/CpuTestSimd32.cs | 325 ++ src/Ryujinx.Tests/Cpu/CpuTestSimdCrypto.cs | 145 + src/Ryujinx.Tests/Cpu/CpuTestSimdCrypto32.cs | 155 + src/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs | 674 +++ src/Ryujinx.Tests/Cpu/CpuTestSimdCvt32.cs | 431 ++ src/Ryujinx.Tests/Cpu/CpuTestSimdExt.cs | 70 + src/Ryujinx.Tests/Cpu/CpuTestSimdFcond.cs | 235 + src/Ryujinx.Tests/Cpu/CpuTestSimdFmov.cs | 60 + src/Ryujinx.Tests/Cpu/CpuTestSimdImm.cs | 395 ++ src/Ryujinx.Tests/Cpu/CpuTestSimdIns.cs | 689 +++ src/Ryujinx.Tests/Cpu/CpuTestSimdLogical32.cs | 153 + src/Ryujinx.Tests/Cpu/CpuTestSimdMemory32.cs | 331 ++ src/Ryujinx.Tests/Cpu/CpuTestSimdMov32.cs | 599 +++ src/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs | 4009 +++++++++++++++ src/Ryujinx.Tests/Cpu/CpuTestSimdReg32.cs | 883 ++++ src/Ryujinx.Tests/Cpu/CpuTestSimdRegElem.cs | 191 + src/Ryujinx.Tests/Cpu/CpuTestSimdRegElem32.cs | 80 + src/Ryujinx.Tests/Cpu/CpuTestSimdRegElemF.cs | 445 ++ src/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs | 1111 ++++ src/Ryujinx.Tests/Cpu/CpuTestSimdShImm32.cs | 315 ++ src/Ryujinx.Tests/Cpu/CpuTestSimdTbl.cs | 317 ++ src/Ryujinx.Tests/Cpu/CpuTestSystem.cs | 69 + src/Ryujinx.Tests/Cpu/CpuTestT32Alu.cs | 1014 ++++ src/Ryujinx.Tests/Cpu/CpuTestT32Flow.cs | 167 + src/Ryujinx.Tests/Cpu/CpuTestT32Mem.cs | 520 ++ src/Ryujinx.Tests/Cpu/CpuTestThumb.cs | 878 ++++ src/Ryujinx.Tests/Cpu/EnvironmentTests.cs | 91 + .../Cpu/PrecomputedMemoryThumbTestCase.cs | 10 + src/Ryujinx.Tests/Cpu/PrecomputedThumbTestCase.cs | 9 + src/Ryujinx.Tests/HLE/SoftwareKeyboardTests.cs | 71 + src/Ryujinx.Tests/Memory/MockMemoryManager.cs | 53 + src/Ryujinx.Tests/Memory/PartialUnmaps.cs | 469 ++ src/Ryujinx.Tests/Ryujinx.Tests.csproj | 50 + src/Ryujinx.Tests/Time/TimeZoneRuleTests.cs | 18 + src/Ryujinx.Tests/TreeDictionaryTests.cs | 244 + .../App/ApplicationAddedEventArgs.cs | 9 + .../App/ApplicationCountUpdatedEventArgs.cs | 10 + src/Ryujinx.Ui.Common/App/ApplicationData.cs | 23 + .../App/ApplicationJsonSerializerContext.cs | 10 + src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs | 923 ++++ src/Ryujinx.Ui.Common/App/ApplicationMetadata.cs | 10 + .../Configuration/AudioBackend.cs | 14 + .../Configuration/ConfigurationFileFormat.cs | 393 ++ .../ConfigurationFileFormatSettings.cs | 9 + .../ConfigurationJsonSerializerContext.cs | 10 + .../Configuration/ConfigurationLoadResult.cs | 9 + .../Configuration/ConfigurationState.cs | 1456 ++++++ src/Ryujinx.Ui.Common/Configuration/FileTypes.cs | 12 + .../Configuration/LoggerModule.cs | 94 + .../Configuration/System/Language.cs | 28 + .../Configuration/System/Region.cs | 17 + .../Configuration/Ui/ColumnSort.cs | 8 + .../Configuration/Ui/GuiColumns.cs | 16 + .../Configuration/Ui/ShownFileTypes.cs | 12 + src/Ryujinx.Ui.Common/DiscordIntegrationModule.cs | 98 + .../Extensions/FileTypeExtensions.cs | 25 + src/Ryujinx.Ui.Common/Helper/CommandLineState.cs | 88 + src/Ryujinx.Ui.Common/Helper/ConsoleHelper.cs | 50 + .../Helper/FileAssociationHelper.cs | 198 + src/Ryujinx.Ui.Common/Helper/ObjectiveC.cs | 97 + src/Ryujinx.Ui.Common/Helper/OpenHelper.cs | 112 + src/Ryujinx.Ui.Common/Helper/SetupValidator.cs | 118 + src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApi.cs | 57 + .../Models/Amiibo/AmiiboApiGamesSwitch.cs | 15 + .../Models/Amiibo/AmiiboApiUsage.cs | 12 + src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboJson.cs | 14 + .../Models/Amiibo/AmiiboJsonSerializerContext.cs | 9 + .../Github/GithubReleaseAssetJsonResponse.cs | 9 + .../Models/Github/GithubReleasesJsonResponse.cs | 10 + .../Github/GithubReleasesJsonSerializerContext.cs | 9 + .../Resources/Controller_JoyConLeft.svg | 1 + .../Resources/Controller_JoyConPair.svg | 1 + .../Resources/Controller_JoyConRight.svg | 1 + .../Resources/Controller_ProCon.svg | 1 + src/Ryujinx.Ui.Common/Resources/Icon_NCA.png | Bin 0 -> 10190 bytes src/Ryujinx.Ui.Common/Resources/Icon_NRO.png | Bin 0 -> 10254 bytes src/Ryujinx.Ui.Common/Resources/Icon_NSO.png | Bin 0 -> 10354 bytes src/Ryujinx.Ui.Common/Resources/Icon_NSP.png | Bin 0 -> 9899 bytes src/Ryujinx.Ui.Common/Resources/Icon_XCI.png | Bin 0 -> 9809 bytes src/Ryujinx.Ui.Common/Resources/Logo_Amiibo.png | Bin 0 -> 10573 bytes .../Resources/Logo_Discord_Dark.png | Bin 0 -> 9835 bytes .../Resources/Logo_Discord_Light.png | Bin 0 -> 10765 bytes .../Resources/Logo_GitHub_Dark.png | Bin 0 -> 4837 bytes .../Resources/Logo_GitHub_Light.png | Bin 0 -> 5166 bytes .../Resources/Logo_Patreon_Dark.png | Bin 0 -> 52210 bytes .../Resources/Logo_Patreon_Light.png | Bin 0 -> 29395 bytes src/Ryujinx.Ui.Common/Resources/Logo_Ryujinx.png | Bin 0 -> 52972 bytes .../Resources/Logo_Twitter_Dark.png | Bin 0 -> 18385 bytes .../Resources/Logo_Twitter_Light.png | Bin 0 -> 19901 bytes src/Ryujinx.Ui.Common/Ryujinx.Ui.Common.csproj | 57 + src/Ryujinx.Ui.Common/UserError.cs | 39 + src/Ryujinx.Ui.LocaleGenerator/LocaleGenerator.cs | 30 + .../Ryujinx.Ui.LocaleGenerator.csproj | 18 + src/Ryujinx/Input/GTK3/GTK3Keyboard.cs | 204 + src/Ryujinx/Input/GTK3/GTK3KeyboardDriver.cs | 93 + src/Ryujinx/Input/GTK3/GTK3MappingHelper.cs | 178 + src/Ryujinx/Input/GTK3/GTK3Mouse.cs | 89 + src/Ryujinx/Input/GTK3/GTK3MouseDriver.cs | 106 + src/Ryujinx/Modules/Updater/UpdateDialog.cs | 83 + src/Ryujinx/Modules/Updater/UpdateDialog.glade | 127 + src/Ryujinx/Modules/Updater/Updater.cs | 619 +++ src/Ryujinx/Program.cs | 342 ++ src/Ryujinx/Ryujinx.csproj | 103 + src/Ryujinx/Ryujinx.ico | Bin 0 -> 108122 bytes src/Ryujinx/Ui/Applet/ErrorAppletDialog.cs | 31 + .../Ui/Applet/GtkDynamicTextInputHandler.cs | 108 + src/Ryujinx/Ui/Applet/GtkHostUiHandler.cs | 213 + src/Ryujinx/Ui/Applet/GtkHostUiTheme.cs | 90 + src/Ryujinx/Ui/Applet/SwkbdAppletDialog.cs | 89 + src/Ryujinx/Ui/GLRenderer.cs | 135 + src/Ryujinx/Ui/Helper/MetalHelper.cs | 134 + src/Ryujinx/Ui/Helper/SortHelper.cs | 94 + src/Ryujinx/Ui/Helper/ThemeHelper.cs | 35 + src/Ryujinx/Ui/MainWindow.cs | 1834 +++++++ src/Ryujinx/Ui/MainWindow.glade | 1006 ++++ src/Ryujinx/Ui/OpenToolkitBindingsContext.cs | 20 + src/Ryujinx/Ui/RendererWidgetBase.cs | 778 +++ src/Ryujinx/Ui/SPBOpenGLContext.cs | 47 + src/Ryujinx/Ui/StatusUpdatedEventArgs.cs | 28 + src/Ryujinx/Ui/VKRenderer.cs | 93 + .../Ui/Widgets/GameTableContextMenu.Designer.cs | 220 + src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs | 602 +++ src/Ryujinx/Ui/Widgets/GtkDialog.cs | 114 + src/Ryujinx/Ui/Widgets/GtkInputDialog.cs | 37 + src/Ryujinx/Ui/Widgets/ProfileDialog.cs | 57 + src/Ryujinx/Ui/Widgets/ProfileDialog.glade | 124 + src/Ryujinx/Ui/Widgets/RawInputToTextEntry.cs | 27 + src/Ryujinx/Ui/Widgets/UserErrorDialog.cs | 123 + src/Ryujinx/Ui/Windows/AboutWindow.Designer.cs | 493 ++ src/Ryujinx/Ui/Windows/AboutWindow.cs | 80 + src/Ryujinx/Ui/Windows/AmiiboWindow.Designer.cs | 194 + src/Ryujinx/Ui/Windows/AmiiboWindow.cs | 387 ++ src/Ryujinx/Ui/Windows/AvatarWindow.cs | 294 ++ src/Ryujinx/Ui/Windows/CheatWindow.cs | 154 + src/Ryujinx/Ui/Windows/CheatWindow.glade | 135 + src/Ryujinx/Ui/Windows/ControllerWindow.cs | 1222 +++++ src/Ryujinx/Ui/Windows/ControllerWindow.glade | 2241 ++++++++ src/Ryujinx/Ui/Windows/DlcWindow.cs | 273 + src/Ryujinx/Ui/Windows/DlcWindow.glade | 202 + src/Ryujinx/Ui/Windows/SettingsWindow.cs | 816 +++ src/Ryujinx/Ui/Windows/SettingsWindow.glade | 3066 +++++++++++ src/Ryujinx/Ui/Windows/TitleUpdateWindow.cs | 208 + src/Ryujinx/Ui/Windows/TitleUpdateWindow.glade | 214 + .../Windows/UserProfilesManagerWindow.Designer.cs | 256 + .../Ui/Windows/UserProfilesManagerWindow.cs | 331 ++ src/Spv.Generator/Autogenerated/CoreGrammar.cs | 5315 +++++++++++++++++++ .../Autogenerated/GlslStd450Grammar.cs | 441 ++ src/Spv.Generator/Autogenerated/OpenClGrammar.cs | 841 +++ src/Spv.Generator/ConstantKey.cs | 30 + src/Spv.Generator/DeterministicHashCode.cs | 109 + src/Spv.Generator/DeterministicStringKey.cs | 30 + src/Spv.Generator/GeneratorPool.cs | 58 + src/Spv.Generator/Instruction.cs | 247 + src/Spv.Generator/InstructionOperands.cs | 72 + src/Spv.Generator/LICENSE | 23 + src/Spv.Generator/LiteralInteger.cs | 105 + src/Spv.Generator/LiteralString.cs | 54 + src/Spv.Generator/Module.cs | 365 ++ src/Spv.Generator/Operand.cs | 14 + src/Spv.Generator/OperandType.cs | 10 + src/Spv.Generator/Spv.Generator.csproj | 7 + src/Spv.Generator/TypeDeclarationKey.cs | 30 + src/Spv.Generator/spirv.cs | 1625 ++++++ 3465 files changed, 494560 insertions(+) create mode 100644 src/ARMeilleure/ARMeilleure.csproj create mode 100644 src/ARMeilleure/Allocators.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/Arm64Optimizer.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/ArmCondition.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/ArmExtensionType.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/ArmShiftType.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/Assembler.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/CallingConvention.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/CodeGenCommon.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/CodeGenerator.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/CodeGeneratorIntrinsic.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/HardwareCapabilities.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/IntrinsicInfo.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/IntrinsicTable.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/IntrinsicType.cs create mode 100644 src/ARMeilleure/CodeGen/Arm64/PreAllocator.cs create mode 100644 src/ARMeilleure/CodeGen/CompiledFunction.cs create mode 100644 src/ARMeilleure/CodeGen/Linking/RelocEntry.cs create mode 100644 src/ARMeilleure/CodeGen/Linking/RelocInfo.cs create mode 100644 src/ARMeilleure/CodeGen/Linking/Symbol.cs create mode 100644 src/ARMeilleure/CodeGen/Linking/SymbolType.cs create mode 100644 src/ARMeilleure/CodeGen/Optimizations/BlockPlacement.cs create mode 100644 src/ARMeilleure/CodeGen/Optimizations/ConstantFolding.cs create mode 100644 src/ARMeilleure/CodeGen/Optimizations/Optimizer.cs create mode 100644 src/ARMeilleure/CodeGen/Optimizations/Simplification.cs create mode 100644 src/ARMeilleure/CodeGen/Optimizations/TailMerge.cs create mode 100644 src/ARMeilleure/CodeGen/PreAllocatorCommon.cs create mode 100644 src/ARMeilleure/CodeGen/RegisterAllocators/AllocationResult.cs create mode 100644 src/ARMeilleure/CodeGen/RegisterAllocators/CopyResolver.cs create mode 100644 src/ARMeilleure/CodeGen/RegisterAllocators/HybridAllocator.cs create mode 100644 src/ARMeilleure/CodeGen/RegisterAllocators/IRegisterAllocator.cs create mode 100644 src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs create mode 100644 src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs create mode 100644 src/ARMeilleure/CodeGen/RegisterAllocators/LiveIntervalList.cs create mode 100644 src/ARMeilleure/CodeGen/RegisterAllocators/LiveRange.cs create mode 100644 src/ARMeilleure/CodeGen/RegisterAllocators/RegisterMasks.cs create mode 100644 src/ARMeilleure/CodeGen/RegisterAllocators/StackAllocator.cs create mode 100644 src/ARMeilleure/CodeGen/RegisterAllocators/UseList.cs create mode 100644 src/ARMeilleure/CodeGen/Unwinding/UnwindInfo.cs create mode 100644 src/ARMeilleure/CodeGen/Unwinding/UnwindPseudoOp.cs create mode 100644 src/ARMeilleure/CodeGen/Unwinding/UnwindPushEntry.cs create mode 100644 src/ARMeilleure/CodeGen/X86/Assembler.cs create mode 100644 src/ARMeilleure/CodeGen/X86/AssemblerTable.cs create mode 100644 src/ARMeilleure/CodeGen/X86/CallConvName.cs create mode 100644 src/ARMeilleure/CodeGen/X86/CallingConvention.cs create mode 100644 src/ARMeilleure/CodeGen/X86/CodeGenCommon.cs create mode 100644 src/ARMeilleure/CodeGen/X86/CodeGenContext.cs create mode 100644 src/ARMeilleure/CodeGen/X86/CodeGenerator.cs create mode 100644 src/ARMeilleure/CodeGen/X86/HardwareCapabilities.cs create mode 100644 src/ARMeilleure/CodeGen/X86/IntrinsicInfo.cs create mode 100644 src/ARMeilleure/CodeGen/X86/IntrinsicTable.cs create mode 100644 src/ARMeilleure/CodeGen/X86/IntrinsicType.cs create mode 100644 src/ARMeilleure/CodeGen/X86/Mxcsr.cs create mode 100644 src/ARMeilleure/CodeGen/X86/PreAllocator.cs create mode 100644 src/ARMeilleure/CodeGen/X86/PreAllocatorSystemV.cs create mode 100644 src/ARMeilleure/CodeGen/X86/PreAllocatorWindows.cs create mode 100644 src/ARMeilleure/CodeGen/X86/X86Condition.cs create mode 100644 src/ARMeilleure/CodeGen/X86/X86Instruction.cs create mode 100644 src/ARMeilleure/CodeGen/X86/X86Optimizer.cs create mode 100644 src/ARMeilleure/CodeGen/X86/X86Register.cs create mode 100644 src/ARMeilleure/Common/AddressTable.cs create mode 100644 src/ARMeilleure/Common/Allocator.cs create mode 100644 src/ARMeilleure/Common/ArenaAllocator.cs create mode 100644 src/ARMeilleure/Common/BitMap.cs create mode 100644 src/ARMeilleure/Common/BitUtils.cs create mode 100644 src/ARMeilleure/Common/Counter.cs create mode 100644 src/ARMeilleure/Common/EntryTable.cs create mode 100644 src/ARMeilleure/Common/EnumUtils.cs create mode 100644 src/ARMeilleure/Common/NativeAllocator.cs create mode 100644 src/ARMeilleure/Decoders/Block.cs create mode 100644 src/ARMeilleure/Decoders/Condition.cs create mode 100644 src/ARMeilleure/Decoders/DataOp.cs create mode 100644 src/ARMeilleure/Decoders/Decoder.cs create mode 100644 src/ARMeilleure/Decoders/DecoderHelper.cs create mode 100644 src/ARMeilleure/Decoders/DecoderMode.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32Adr.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32Alu.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32AluBf.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32AluImm.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32AluImm16.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32AluMla.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32AluReg.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32AluRsImm.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32AluRsReg.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32AluUmull.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32AluUx.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32BImm.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32BReg.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32Exception.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32HasSetFlags.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32Mem.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32MemEx.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32MemMult.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32MemReg.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32MemRsImm.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32Simd.cs create mode 100644 src/ARMeilleure/Decoders/IOpCode32SimdImm.cs create mode 100644 src/ARMeilleure/Decoders/IOpCodeAlu.cs create mode 100644 src/ARMeilleure/Decoders/IOpCodeAluImm.cs create mode 100644 src/ARMeilleure/Decoders/IOpCodeAluRs.cs create mode 100644 src/ARMeilleure/Decoders/IOpCodeAluRx.cs create mode 100644 src/ARMeilleure/Decoders/IOpCodeBImm.cs create mode 100644 src/ARMeilleure/Decoders/IOpCodeCond.cs create mode 100644 src/ARMeilleure/Decoders/IOpCodeLit.cs create mode 100644 src/ARMeilleure/Decoders/IOpCodeSimd.cs create mode 100644 src/ARMeilleure/Decoders/InstDescriptor.cs create mode 100644 src/ARMeilleure/Decoders/InstEmitter.cs create mode 100644 src/ARMeilleure/Decoders/IntType.cs create mode 100644 src/ARMeilleure/Decoders/OpCode.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32Alu.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32AluBf.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32AluImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32AluImm16.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32AluMla.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32AluReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32AluRsImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32AluRsReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32AluUmull.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32AluUx.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32BImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32BReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32Exception.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32Mem.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32MemImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32MemImm8.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32MemLdEx.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32MemMult.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32MemReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32MemRsImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32MemStEx.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32Mrs.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32MsrReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32Sat.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32Sat16.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32Simd.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdBase.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdBinary.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdCmpZ.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdCvtFI.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdCvtTB.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdDupElem.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdDupGP.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdExt.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdImm44.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdLong.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdMemImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdMemMult.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdMemPair.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdMemSingle.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdMovGp.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdMovGpDouble.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdMovGpElem.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdMovn.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdRegElem.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdRegElemLong.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdRegLong.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdRegS.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdRegWide.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdRev.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdS.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdSel.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdShImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdShImmLong.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdShImmNarrow.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdSpecial.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdSqrte.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32SimdTbl.cs create mode 100644 src/ARMeilleure/Decoders/OpCode32System.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeAdr.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeAlu.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeAluBinary.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeAluImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeAluRs.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeAluRx.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeBImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeBImmAl.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeBImmCmp.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeBImmCond.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeBImmTest.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeBReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeBfm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeCcmp.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeCcmpImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeCcmpReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeCsel.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeException.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeMem.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeMemEx.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeMemImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeMemLit.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeMemPair.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeMemReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeMov.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeMul.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimd.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdCvt.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdExt.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdFcond.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdFmov.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdHelper.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdIns.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdMemImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdMemLit.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdMemMs.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdMemPair.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdMemReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdMemSs.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdRegElem.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdRegElemF.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdShImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSimdTbl.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeSystem.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16AddSubImm3.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16AddSubReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16AddSubSp.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16Adr.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16AluImm8.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16AluImmZero.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16AluRegHigh.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16AluRegLow.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16AluUx.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16BImm11.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16BImm8.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16BImmCmp.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16BReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16Exception.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16IfThen.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16MemImm5.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16MemLit.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16MemMult.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16MemReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16MemSp.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16MemStack.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16ShiftImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16ShiftReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT16SpRel.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32Alu.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32AluBf.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32AluImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32AluImm12.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32AluMla.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32AluReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32AluRsImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32AluUmull.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32AluUx.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32BImm20.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32BImm24.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32MemImm12.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32MemImm8.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32MemImm8D.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32MemLdEx.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32MemMult.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32MemRsImm.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32MemStEx.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32MovImm16.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32ShiftReg.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeT32Tb.cs create mode 100644 src/ARMeilleure/Decoders/OpCodeTable.cs create mode 100644 src/ARMeilleure/Decoders/Optimizations/TailCallRemover.cs create mode 100644 src/ARMeilleure/Decoders/RegisterSize.cs create mode 100644 src/ARMeilleure/Decoders/ShiftType.cs create mode 100644 src/ARMeilleure/Diagnostics/IRDumper.cs create mode 100644 src/ARMeilleure/Diagnostics/Logger.cs create mode 100644 src/ARMeilleure/Diagnostics/PassName.cs create mode 100644 src/ARMeilleure/Diagnostics/Symbols.cs create mode 100644 src/ARMeilleure/Diagnostics/TranslatorEventSource.cs create mode 100644 src/ARMeilleure/Instructions/CryptoHelper.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitAlu.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitAlu32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitAluHelper.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitBfm.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitCcmp.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitCsel.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitDiv.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitException.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitException32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitFlow.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitFlow32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitFlowHelper.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitHash.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitHash32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitHashHelper.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitHelper.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitMemory.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitMemory32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitMemoryEx.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitMemoryEx32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitMemoryExHelper.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitMove.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitMul.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitMul32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdCmp.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdCmp32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdCrypto.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdCrypto32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdCvt.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdCvt32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdHash.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdHash32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdHashHelper.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdHelper.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdHelper32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdHelper32Arm64.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdHelperArm64.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdLogical.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdLogical32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdMemory.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdMemory32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdMove.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdMove32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdShift.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSimdShift32.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSystem.cs create mode 100644 src/ARMeilleure/Instructions/InstEmitSystem32.cs create mode 100644 src/ARMeilleure/Instructions/InstName.cs create mode 100644 src/ARMeilleure/Instructions/NativeInterface.cs create mode 100644 src/ARMeilleure/Instructions/SoftFallback.cs create mode 100644 src/ARMeilleure/Instructions/SoftFloat.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/BasicBlock.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/BasicBlockFrequency.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/Comparison.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/IIntrusiveListNode.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/Instruction.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/Intrinsic.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/IntrusiveList.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/MemoryOperand.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/Multiplier.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/Operand.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/OperandKind.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/OperandType.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/Operation.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/PhiOperation.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/Register.cs create mode 100644 src/ARMeilleure/IntermediateRepresentation/RegisterType.cs create mode 100644 src/ARMeilleure/Memory/IJitMemoryAllocator.cs create mode 100644 src/ARMeilleure/Memory/IJitMemoryBlock.cs create mode 100644 src/ARMeilleure/Memory/IMemoryManager.cs create mode 100644 src/ARMeilleure/Memory/InvalidAccessException.cs create mode 100644 src/ARMeilleure/Memory/MemoryManagerType.cs create mode 100644 src/ARMeilleure/Memory/ReservedRegion.cs create mode 100644 src/ARMeilleure/Native/JitSupportDarwin.cs create mode 100644 src/ARMeilleure/Native/libs/libarmeilleure-jitsupport.dylib create mode 100644 src/ARMeilleure/Native/macos_jit_support/Makefile create mode 100644 src/ARMeilleure/Native/macos_jit_support/support.c create mode 100644 src/ARMeilleure/Optimizations.cs create mode 100644 src/ARMeilleure/Signal/NativeSignalHandler.cs create mode 100644 src/ARMeilleure/Signal/TestMethods.cs create mode 100644 src/ARMeilleure/Signal/UnixSignalHandlerRegistration.cs create mode 100644 src/ARMeilleure/Signal/WindowsPartialUnmapHandler.cs create mode 100644 src/ARMeilleure/Signal/WindowsSignalHandlerRegistration.cs create mode 100644 src/ARMeilleure/State/Aarch32Mode.cs create mode 100644 src/ARMeilleure/State/ExceptionCallback.cs create mode 100644 src/ARMeilleure/State/ExecutionContext.cs create mode 100644 src/ARMeilleure/State/ExecutionMode.cs create mode 100644 src/ARMeilleure/State/FPCR.cs create mode 100644 src/ARMeilleure/State/FPException.cs create mode 100644 src/ARMeilleure/State/FPRoundingMode.cs create mode 100644 src/ARMeilleure/State/FPSCR.cs create mode 100644 src/ARMeilleure/State/FPSR.cs create mode 100644 src/ARMeilleure/State/FPState.cs create mode 100644 src/ARMeilleure/State/FPType.cs create mode 100644 src/ARMeilleure/State/ICounter.cs create mode 100644 src/ARMeilleure/State/NativeContext.cs create mode 100644 src/ARMeilleure/State/PState.cs create mode 100644 src/ARMeilleure/State/RegisterAlias.cs create mode 100644 src/ARMeilleure/State/RegisterConsts.cs create mode 100644 src/ARMeilleure/State/V128.cs create mode 100644 src/ARMeilleure/Statistics.cs create mode 100644 src/ARMeilleure/Translation/ArmEmitterContext.cs create mode 100644 src/ARMeilleure/Translation/Cache/CacheEntry.cs create mode 100644 src/ARMeilleure/Translation/Cache/CacheMemoryAllocator.cs create mode 100644 src/ARMeilleure/Translation/Cache/JitCache.cs create mode 100644 src/ARMeilleure/Translation/Cache/JitCacheInvalidation.cs create mode 100644 src/ARMeilleure/Translation/Cache/JitUnwindWindows.cs create mode 100644 src/ARMeilleure/Translation/Compiler.cs create mode 100644 src/ARMeilleure/Translation/CompilerContext.cs create mode 100644 src/ARMeilleure/Translation/CompilerOptions.cs create mode 100644 src/ARMeilleure/Translation/ControlFlowGraph.cs create mode 100644 src/ARMeilleure/Translation/DelegateHelper.cs create mode 100644 src/ARMeilleure/Translation/DelegateInfo.cs create mode 100644 src/ARMeilleure/Translation/Delegates.cs create mode 100644 src/ARMeilleure/Translation/DispatcherFunction.cs create mode 100644 src/ARMeilleure/Translation/Dominance.cs create mode 100644 src/ARMeilleure/Translation/EmitterContext.cs create mode 100644 src/ARMeilleure/Translation/GuestFunction.cs create mode 100644 src/ARMeilleure/Translation/IntervalTree.cs create mode 100644 src/ARMeilleure/Translation/PTC/EncodingCache.cs create mode 100644 src/ARMeilleure/Translation/PTC/IPtcLoadState.cs create mode 100644 src/ARMeilleure/Translation/PTC/Ptc.cs create mode 100644 src/ARMeilleure/Translation/PTC/PtcFormatter.cs create mode 100644 src/ARMeilleure/Translation/PTC/PtcLoadingState.cs create mode 100644 src/ARMeilleure/Translation/PTC/PtcProfiler.cs create mode 100644 src/ARMeilleure/Translation/PTC/PtcState.cs create mode 100644 src/ARMeilleure/Translation/RegisterToLocal.cs create mode 100644 src/ARMeilleure/Translation/RegisterUsage.cs create mode 100644 src/ARMeilleure/Translation/RejitRequest.cs create mode 100644 src/ARMeilleure/Translation/SsaConstruction.cs create mode 100644 src/ARMeilleure/Translation/SsaDeconstruction.cs create mode 100644 src/ARMeilleure/Translation/TranslatedFunction.cs create mode 100644 src/ARMeilleure/Translation/Translator.cs create mode 100644 src/ARMeilleure/Translation/TranslatorCache.cs create mode 100644 src/ARMeilleure/Translation/TranslatorQueue.cs create mode 100644 src/ARMeilleure/Translation/TranslatorStubs.cs create mode 100644 src/ARMeilleure/Translation/TranslatorTestMethods.cs create mode 100644 src/Ryujinx.Audio.Backends.OpenAL/OpenALAudioBuffer.cs create mode 100644 src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs create mode 100644 src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs create mode 100644 src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj create mode 100644 src/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj create mode 100644 src/Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs create mode 100644 src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs create mode 100644 src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIo.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoBackend.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoChannelId.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoContext.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceAim.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceContext.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoError.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoException.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoFormat.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoOutStreamContext.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dll create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dylib create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.so create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/SoundIoAudioBuffer.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs create mode 100644 src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs create mode 100644 src/Ryujinx.Audio/AudioManager.cs create mode 100644 src/Ryujinx.Audio/Backends/Common/BackendHelper.cs create mode 100644 src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs create mode 100644 src/Ryujinx.Audio/Backends/Common/HardwareDeviceSessionOutputBase.cs create mode 100644 src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs create mode 100644 src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceSession.cs create mode 100644 src/Ryujinx.Audio/Backends/CompatLayer/Downmixing.cs create mode 100644 src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs create mode 100644 src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionInput.cs create mode 100644 src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs create mode 100644 src/Ryujinx.Audio/Common/AudioBuffer.cs create mode 100644 src/Ryujinx.Audio/Common/AudioDeviceSession.cs create mode 100644 src/Ryujinx.Audio/Common/AudioDeviceState.cs create mode 100644 src/Ryujinx.Audio/Common/AudioInputConfiguration.cs create mode 100644 src/Ryujinx.Audio/Common/AudioOutputConfiguration.cs create mode 100644 src/Ryujinx.Audio/Common/AudioUserBuffer.cs create mode 100644 src/Ryujinx.Audio/Common/SampleFormat.cs create mode 100644 src/Ryujinx.Audio/Constants.cs create mode 100644 src/Ryujinx.Audio/Input/AudioInputManager.cs create mode 100644 src/Ryujinx.Audio/Input/AudioInputSystem.cs create mode 100644 src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs create mode 100644 src/Ryujinx.Audio/Integration/IHardwareDevice.cs create mode 100644 src/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs create mode 100644 src/Ryujinx.Audio/Integration/IHardwareDeviceSession.cs create mode 100644 src/Ryujinx.Audio/Integration/IWritableEvent.cs create mode 100644 src/Ryujinx.Audio/Output/AudioOutputManager.cs create mode 100644 src/Ryujinx.Audio/Output/AudioOutputSystem.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/AuxiliaryBufferAddresses.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/EffectType.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/MemoryPoolUserState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/NodeIdType.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/NodeStates.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/PerformanceEntryType.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/PlayState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/ReverbEarlyMode.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/ReverbLateMode.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/SinkType.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/WaveBuffer.cs create mode 100644 src/Ryujinx.Audio/Renderer/Common/WorkBufferAllocator.cs create mode 100644 src/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs create mode 100644 src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSession.cs create mode 100644 src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSessionRegistry.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/CaptureBufferCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Effect/DecayDelay.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLine.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLineReverb3d.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Effect/ExponentialMovingAverage.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/Effect/IDelayLine.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/AdpcmLoopContext.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/AuxiliaryBufferHeader.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/CompressorState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/Reverb3dState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/State/ReverbState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/AudioRendererConfiguration.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/BiquadFilterParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterStatistics.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion2.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion2.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/EffectResultState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/EffectState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/IEffectInParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/IEffectOutStatus.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolInParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolOutStatus.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/MixParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceInParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceOutStatus.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/RendererInfoOutStatus.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Sink/CircularBufferParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/Sink/DeviceParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/SinkInParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/SinkOutStatus.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameterHeader.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/VoiceChannelResourceInParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs create mode 100644 src/Ryujinx.Audio/Renderer/Parameter/VoiceOutStatus.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Effect/UsageState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/MemoryPool/AddressInfo.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/MemoryPool/MemoryPoolState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceDetailEntry.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceEntry.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceHeader.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion2.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryAddresses.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion2.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/RendererSystemContext.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Sink/SinkContext.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererExecutionMode.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererRenderingDevice.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Types/PlayState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerBufferState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerManager.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Voice/VoiceChannelResource.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Voice/VoiceContext.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs create mode 100644 src/Ryujinx.Audio/Renderer/Server/Voice/WaveBuffer.cs create mode 100644 src/Ryujinx.Audio/Renderer/Utils/AudioProcessorMemoryManager.cs create mode 100644 src/Ryujinx.Audio/Renderer/Utils/BitArray.cs create mode 100644 src/Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs create mode 100644 src/Ryujinx.Audio/Renderer/Utils/Mailbox.cs create mode 100644 src/Ryujinx.Audio/Renderer/Utils/Math/Matrix2x2.cs create mode 100644 src/Ryujinx.Audio/Renderer/Utils/Math/Matrix6x6.cs create mode 100644 src/Ryujinx.Audio/Renderer/Utils/Math/MatrixHelper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Utils/Math/Vector6.cs create mode 100644 src/Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs create mode 100644 src/Ryujinx.Audio/Renderer/Utils/SpanMemoryManager.cs create mode 100644 src/Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs create mode 100644 src/Ryujinx.Audio/ResultCode.cs create mode 100644 src/Ryujinx.Audio/Ryujinx.Audio.csproj create mode 100644 src/Ryujinx.Ava/App.axaml create mode 100644 src/Ryujinx.Ava/App.axaml.cs create mode 100644 src/Ryujinx.Ava/AppHost.cs create mode 100644 src/Ryujinx.Ava/Assets/Fonts/SegoeFluentIcons.ttf create mode 100644 src/Ryujinx.Ava/Assets/Locales/de_DE.json create mode 100644 src/Ryujinx.Ava/Assets/Locales/el_GR.json create mode 100644 src/Ryujinx.Ava/Assets/Locales/en_US.json create mode 100644 src/Ryujinx.Ava/Assets/Locales/es_ES.json create mode 100644 src/Ryujinx.Ava/Assets/Locales/fr_FR.json create mode 100644 src/Ryujinx.Ava/Assets/Locales/it_IT.json create mode 100644 src/Ryujinx.Ava/Assets/Locales/ja_JP.json create mode 100644 src/Ryujinx.Ava/Assets/Locales/ko_KR.json create mode 100644 src/Ryujinx.Ava/Assets/Locales/pl_PL.json create mode 100644 src/Ryujinx.Ava/Assets/Locales/pt_BR.json create mode 100644 src/Ryujinx.Ava/Assets/Locales/ru_RU.json create mode 100644 src/Ryujinx.Ava/Assets/Locales/tr_TR.json create mode 100644 src/Ryujinx.Ava/Assets/Locales/uk_UA.json create mode 100644 src/Ryujinx.Ava/Assets/Locales/zh_CN.json create mode 100644 src/Ryujinx.Ava/Assets/Locales/zh_TW.json create mode 100644 src/Ryujinx.Ava/Assets/Styles/BaseDark.xaml create mode 100644 src/Ryujinx.Ava/Assets/Styles/BaseLight.xaml create mode 100644 src/Ryujinx.Ava/Assets/Styles/Styles.xaml create mode 100644 src/Ryujinx.Ava/Common/ApplicationHelper.cs create mode 100644 src/Ryujinx.Ava/Common/ApplicationSort.cs create mode 100644 src/Ryujinx.Ava/Common/KeyboardHotkeyState.cs create mode 100644 src/Ryujinx.Ava/Common/Locale/LocaleExtension.cs create mode 100644 src/Ryujinx.Ava/Common/Locale/LocaleManager.cs create mode 100644 src/Ryujinx.Ava/Input/AvaloniaKeyboard.cs create mode 100644 src/Ryujinx.Ava/Input/AvaloniaKeyboardDriver.cs create mode 100644 src/Ryujinx.Ava/Input/AvaloniaKeyboardMappingHelper.cs create mode 100644 src/Ryujinx.Ava/Input/AvaloniaMouse.cs create mode 100644 src/Ryujinx.Ava/Input/AvaloniaMouseDriver.cs create mode 100644 src/Ryujinx.Ava/Modules/Updater/Updater.cs create mode 100644 src/Ryujinx.Ava/Program.cs create mode 100644 src/Ryujinx.Ava/Ryujinx.Ava.csproj create mode 100644 src/Ryujinx.Ava/Ryujinx.ico create mode 100644 src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs create mode 100644 src/Ryujinx.Ava/UI/Applet/AvaloniaDynamicTextInputHandler.cs create mode 100644 src/Ryujinx.Ava/UI/Applet/AvaloniaHostUiTheme.cs create mode 100644 src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml create mode 100644 src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml create mode 100644 src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Controls/GameGridView.axaml create mode 100644 src/Ryujinx.Ava/UI/Controls/GameGridView.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Controls/GameListView.axaml create mode 100644 src/Ryujinx.Ava/UI/Controls/GameListView.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml create mode 100644 src/Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml create mode 100644 src/Ryujinx.Ava/UI/Controls/UpdateWaitWindow.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Helpers/ApplicationOpenedEventArgs.cs create mode 100644 src/Ryujinx.Ava/UI/Helpers/BitmapArrayValueConverter.cs create mode 100644 src/Ryujinx.Ava/UI/Helpers/ButtonKeyAssigner.cs create mode 100644 src/Ryujinx.Ava/UI/Helpers/ContentDialogHelper.cs create mode 100644 src/Ryujinx.Ava/UI/Helpers/Glyph.cs create mode 100644 src/Ryujinx.Ava/UI/Helpers/GlyphValueConverter.cs create mode 100644 src/Ryujinx.Ava/UI/Helpers/HotKeyControl.cs create mode 100644 src/Ryujinx.Ava/UI/Helpers/KeyValueConverter.cs create mode 100644 src/Ryujinx.Ava/UI/Helpers/LoggerAdapter.cs create mode 100644 src/Ryujinx.Ava/UI/Helpers/MiniCommand.cs create mode 100644 src/Ryujinx.Ava/UI/Helpers/NotificationHelper.cs create mode 100644 src/Ryujinx.Ava/UI/Helpers/OffscreenTextBox.cs create mode 100644 src/Ryujinx.Ava/UI/Helpers/UserErrorDialog.cs create mode 100644 src/Ryujinx.Ava/UI/Helpers/UserResult.cs create mode 100644 src/Ryujinx.Ava/UI/Helpers/Win32NativeInterop.cs create mode 100644 src/Ryujinx.Ava/UI/Models/CheatModel.cs create mode 100644 src/Ryujinx.Ava/UI/Models/CheatsList.cs create mode 100644 src/Ryujinx.Ava/UI/Models/ControllerModel.cs create mode 100644 src/Ryujinx.Ava/UI/Models/DeviceType.cs create mode 100644 src/Ryujinx.Ava/UI/Models/DownloadableContentModel.cs create mode 100644 src/Ryujinx.Ava/UI/Models/Generic/LastPlayedSortComparer.cs create mode 100644 src/Ryujinx.Ava/UI/Models/InputConfiguration.cs create mode 100644 src/Ryujinx.Ava/UI/Models/PlayerModel.cs create mode 100644 src/Ryujinx.Ava/UI/Models/ProfileImageModel.cs create mode 100644 src/Ryujinx.Ava/UI/Models/SaveModel.cs create mode 100644 src/Ryujinx.Ava/UI/Models/StatusUpdatedEventArgs.cs create mode 100644 src/Ryujinx.Ava/UI/Models/TempProfile.cs create mode 100644 src/Ryujinx.Ava/UI/Models/TimeZone.cs create mode 100644 src/Ryujinx.Ava/UI/Models/TitleUpdateModel.cs create mode 100644 src/Ryujinx.Ava/UI/Models/UserProfile.cs create mode 100644 src/Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs create mode 100644 src/Ryujinx.Ava/UI/Renderer/EmbeddedWindowOpenGL.cs create mode 100644 src/Ryujinx.Ava/UI/Renderer/EmbeddedWindowVulkan.cs create mode 100644 src/Ryujinx.Ava/UI/Renderer/OpenTKBindingsContext.cs create mode 100644 src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml create mode 100644 src/Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Renderer/SPBOpenGLContext.cs create mode 100644 src/Ryujinx.Ava/UI/ViewModels/AboutWindowViewModel.cs create mode 100644 src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs create mode 100644 src/Ryujinx.Ava/UI/ViewModels/AvatarProfileViewModel.cs create mode 100644 src/Ryujinx.Ava/UI/ViewModels/BaseModel.cs create mode 100644 src/Ryujinx.Ava/UI/ViewModels/ControllerSettingsViewModel.cs create mode 100644 src/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs create mode 100644 src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs create mode 100644 src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs create mode 100644 src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs create mode 100644 src/Ryujinx.Ava/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs create mode 100644 src/Ryujinx.Ava/UI/ViewModels/UserProfileImageSelectorViewModel.cs create mode 100644 src/Ryujinx.Ava/UI/ViewModels/UserProfileViewModel.cs create mode 100644 src/Ryujinx.Ava/UI/ViewModels/UserSaveManagerViewModel.cs create mode 100644 src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml create mode 100644 src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml create mode 100644 src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml create mode 100644 src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsAudioView.axaml create mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsAudioView.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsCPUView.axaml create mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsCPUView.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsGraphicsView.axaml create mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsGraphicsView.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsHotkeysView.axaml create mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsHotkeysView.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsInputView.axaml create mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsInputView.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsLoggingView.axaml create mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsLoggingView.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsNetworkView.axaml create mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsNetworkView.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsSystemView.axaml create mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsSystemView.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsUIView.axaml create mode 100644 src/Ryujinx.Ava/UI/Views/Settings/SettingsUIView.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Views/User/UserEditorView.axaml create mode 100644 src/Ryujinx.Ava/UI/Views/User/UserEditorView.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml create mode 100644 src/Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml create mode 100644 src/Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml create mode 100644 src/Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml create mode 100644 src/Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml create mode 100644 src/Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml create mode 100644 src/Ryujinx.Ava/UI/Windows/AboutWindow.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml create mode 100644 src/Ryujinx.Ava/UI/Windows/AmiiboWindow.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml create mode 100644 src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml create mode 100644 src/Ryujinx.Ava/UI/Windows/ContentDialogOverlayWindow.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Windows/ControllerSettingsWindow.axaml create mode 100644 src/Ryujinx.Ava/UI/Windows/ControllerSettingsWindow.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml create mode 100644 src/Ryujinx.Ava/UI/Windows/DownloadableContentManagerWindow.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Windows/IconColorPicker.cs create mode 100644 src/Ryujinx.Ava/UI/Windows/MainWindow.axaml create mode 100644 src/Ryujinx.Ava/UI/Windows/MainWindow.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Windows/MotionSettingsWindow.axaml create mode 100644 src/Ryujinx.Ava/UI/Windows/MotionSettingsWindow.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Windows/RumbleSettingsWindow.axaml create mode 100644 src/Ryujinx.Ava/UI/Windows/RumbleSettingsWindow.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml create mode 100644 src/Ryujinx.Ava/UI/Windows/SettingsWindow.axaml.cs create mode 100644 src/Ryujinx.Ava/UI/Windows/StyleableWindow.cs create mode 100644 src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml create mode 100644 src/Ryujinx.Ava/UI/Windows/TitleUpdateWindow.axaml.cs create mode 100644 src/Ryujinx.Common/AsyncWorkQueue.cs create mode 100644 src/Ryujinx.Common/Collections/IntervalTree.cs create mode 100644 src/Ryujinx.Common/Collections/IntrusiveRedBlackTree.cs create mode 100644 src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeImpl.cs create mode 100644 src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeNode.cs create mode 100644 src/Ryujinx.Common/Collections/TreeDictionary.cs create mode 100644 src/Ryujinx.Common/Configuration/AntiAliasing.cs create mode 100644 src/Ryujinx.Common/Configuration/AppDataManager.cs create mode 100644 src/Ryujinx.Common/Configuration/AspectRatioExtensions.cs create mode 100644 src/Ryujinx.Common/Configuration/BackendThreading.cs create mode 100644 src/Ryujinx.Common/Configuration/DownloadableContentContainer.cs create mode 100644 src/Ryujinx.Common/Configuration/DownloadableContentJsonSerializerContext.cs create mode 100644 src/Ryujinx.Common/Configuration/DownloadableContentNca.cs create mode 100644 src/Ryujinx.Common/Configuration/GraphicsBackend.cs create mode 100644 src/Ryujinx.Common/Configuration/GraphicsDebugLevel.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/GamepadInputId.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/GenericControllerInputConfig.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/JoyconConfigControllerStick.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/Motion/CemuHookMotionConfigController.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/Motion/JsonMotionConfigControllerConverter.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionConfigController.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionConfigJsonSerializerContext.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionInputBackendType.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/Motion/StandardMotionConfigController.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/RumbleConfigController.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/StandardControllerInputConfig.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Controller/StickInputId.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/ControllerType.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/GenericInputConfigurationCommon.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/InputBackendType.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/InputConfig.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/InputConfigJsonSerializerContext.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/JsonInputConfigConverter.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Key.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Keyboard/GenericKeyboardInputConfig.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Keyboard/JoyconConfigKeyboardStick.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/Keyboard/StandardKeyboardInputConfig.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/LeftJoyconCommonConfig.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/PlayerIndex.cs create mode 100644 src/Ryujinx.Common/Configuration/Hid/RightJoyconCommonConfig.cs create mode 100644 src/Ryujinx.Common/Configuration/MemoryManagerMode.cs create mode 100644 src/Ryujinx.Common/Configuration/ScalingFilter.cs create mode 100644 src/Ryujinx.Common/Configuration/TitleUpdateMetadata.cs create mode 100644 src/Ryujinx.Common/Configuration/TitleUpdateMetadataJsonSerializerContext.cs create mode 100644 src/Ryujinx.Common/Extensions/BinaryReaderExtensions.cs create mode 100644 src/Ryujinx.Common/Extensions/BinaryWriterExtensions.cs create mode 100644 src/Ryujinx.Common/Extensions/StreamExtensions.cs create mode 100644 src/Ryujinx.Common/GraphicsDriver/DriverUtilities.cs create mode 100644 src/Ryujinx.Common/GraphicsDriver/NVAPI/Nvapi.cs create mode 100644 src/Ryujinx.Common/GraphicsDriver/NVAPI/NvapiUnicodeString.cs create mode 100644 src/Ryujinx.Common/GraphicsDriver/NVAPI/NvdrsApplicationV4.cs create mode 100644 src/Ryujinx.Common/GraphicsDriver/NVAPI/NvdrsProfile.cs create mode 100644 src/Ryujinx.Common/GraphicsDriver/NVAPI/NvdrsSetting.cs create mode 100644 src/Ryujinx.Common/GraphicsDriver/NVThreadedOptimization.cs create mode 100644 src/Ryujinx.Common/Hash128.cs create mode 100644 src/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs create mode 100644 src/Ryujinx.Common/Logging/Formatters/DynamicObjectFormatter.cs create mode 100644 src/Ryujinx.Common/Logging/Formatters/ILogFormatter.cs create mode 100644 src/Ryujinx.Common/Logging/LogClass.cs create mode 100644 src/Ryujinx.Common/Logging/LogEventArgs.cs create mode 100644 src/Ryujinx.Common/Logging/LogEventArgsJson.cs create mode 100644 src/Ryujinx.Common/Logging/LogEventJsonSerializerContext.cs create mode 100644 src/Ryujinx.Common/Logging/LogLevel.cs create mode 100644 src/Ryujinx.Common/Logging/Logger.cs create mode 100644 src/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs create mode 100644 src/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs create mode 100644 src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs create mode 100644 src/Ryujinx.Common/Logging/Targets/ILogTarget.cs create mode 100644 src/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs create mode 100644 src/Ryujinx.Common/Memory/ArrayPtr.cs create mode 100644 src/Ryujinx.Common/Memory/Box.cs create mode 100644 src/Ryujinx.Common/Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs create mode 100644 src/Ryujinx.Common/Memory/ByteMemoryPool.cs create mode 100644 src/Ryujinx.Common/Memory/IArray.cs create mode 100644 src/Ryujinx.Common/Memory/MemoryStreamManager.cs create mode 100644 src/Ryujinx.Common/Memory/PartialUnmaps/NativeReaderWriterLock.cs create mode 100644 src/Ryujinx.Common/Memory/PartialUnmaps/PartialUnmapHelpers.cs create mode 100644 src/Ryujinx.Common/Memory/PartialUnmaps/PartialUnmapState.cs create mode 100644 src/Ryujinx.Common/Memory/PartialUnmaps/ThreadLocalMap.cs create mode 100644 src/Ryujinx.Common/Memory/Ptr.cs create mode 100644 src/Ryujinx.Common/Memory/SpanOrArray.cs create mode 100644 src/Ryujinx.Common/Memory/SpanReader.cs create mode 100644 src/Ryujinx.Common/Memory/SpanWriter.cs create mode 100644 src/Ryujinx.Common/Memory/StructArrayHelpers.cs create mode 100644 src/Ryujinx.Common/Memory/StructByteArrayHelpers.cs create mode 100644 src/Ryujinx.Common/PerformanceCounter.cs create mode 100644 src/Ryujinx.Common/Pools/ObjectPool.cs create mode 100644 src/Ryujinx.Common/Pools/SharedPools.cs create mode 100644 src/Ryujinx.Common/Pools/ThreadStaticArray.cs create mode 100644 src/Ryujinx.Common/ReactiveObject.cs create mode 100644 src/Ryujinx.Common/ReferenceEqualityComparer.cs create mode 100644 src/Ryujinx.Common/ReleaseInformation.cs create mode 100644 src/Ryujinx.Common/Ryujinx.Common.csproj create mode 100644 src/Ryujinx.Common/SystemInfo/LinuxSystemInfo.cs create mode 100644 src/Ryujinx.Common/SystemInfo/MacOSSystemInfo.cs create mode 100644 src/Ryujinx.Common/SystemInfo/SystemInfo.cs create mode 100644 src/Ryujinx.Common/SystemInfo/WindowsSystemInfo.cs create mode 100644 src/Ryujinx.Common/SystemInterop/DisplaySleep.cs create mode 100644 src/Ryujinx.Common/SystemInterop/ForceDpiAware.cs create mode 100644 src/Ryujinx.Common/SystemInterop/GdiPlusHelper.cs create mode 100644 src/Ryujinx.Common/SystemInterop/StdErrAdapter.cs create mode 100644 src/Ryujinx.Common/SystemInterop/WindowsMultimediaTimerResolution.cs create mode 100644 src/Ryujinx.Common/Utilities/BitUtils.cs create mode 100644 src/Ryujinx.Common/Utilities/BitfieldExtensions.cs create mode 100644 src/Ryujinx.Common/Utilities/Buffers.cs create mode 100644 src/Ryujinx.Common/Utilities/CommonJsonContext.cs create mode 100644 src/Ryujinx.Common/Utilities/EmbeddedResources.cs create mode 100644 src/Ryujinx.Common/Utilities/HexUtils.cs create mode 100644 src/Ryujinx.Common/Utilities/JsonHelper.cs create mode 100644 src/Ryujinx.Common/Utilities/MessagePackObjectFormatter.cs create mode 100644 src/Ryujinx.Common/Utilities/NetworkHelpers.cs create mode 100644 src/Ryujinx.Common/Utilities/SpanHelpers.cs create mode 100644 src/Ryujinx.Common/Utilities/StreamUtils.cs create mode 100644 src/Ryujinx.Common/Utilities/TypedStringEnumConverter.cs create mode 100644 src/Ryujinx.Common/Utilities/UInt128Utils.cs create mode 100644 src/Ryujinx.Common/XXHash128.cs create mode 100644 src/Ryujinx.Cpu/AddressSpace.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/Arm/ApFlags.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/Arm/ExceptionClass.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/DummyDiskCacheLoadState.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvAddressSpace.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvAddressSpaceRange.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvApi.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvEngine.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvIpaAllocator.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocation.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocator.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvVcpu.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvVcpuPool.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/HvVm.cs create mode 100644 src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs create mode 100644 src/Ryujinx.Cpu/ExceptionCallbacks.cs create mode 100644 src/Ryujinx.Cpu/ICpuContext.cs create mode 100644 src/Ryujinx.Cpu/ICpuEngine.cs create mode 100644 src/Ryujinx.Cpu/IDiskCacheState.cs create mode 100644 src/Ryujinx.Cpu/IExecutionContext.cs create mode 100644 src/Ryujinx.Cpu/ITickSource.cs create mode 100644 src/Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs create mode 100644 src/Ryujinx.Cpu/Jit/JitCpuContext.cs create mode 100644 src/Ryujinx.Cpu/Jit/JitDiskCacheLoadState.cs create mode 100644 src/Ryujinx.Cpu/Jit/JitEngine.cs create mode 100644 src/Ryujinx.Cpu/Jit/JitExecutionContext.cs create mode 100644 src/Ryujinx.Cpu/Jit/JitMemoryAllocator.cs create mode 100644 src/Ryujinx.Cpu/Jit/JitMemoryBlock.cs create mode 100644 src/Ryujinx.Cpu/Jit/MemoryManager.cs create mode 100644 src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs create mode 100644 src/Ryujinx.Cpu/LoadState.cs create mode 100644 src/Ryujinx.Cpu/MemoryEhMeilleure.cs create mode 100644 src/Ryujinx.Cpu/MemoryHelper.cs create mode 100644 src/Ryujinx.Cpu/MemoryManagerBase.cs create mode 100644 src/Ryujinx.Cpu/PrivateMemoryAllocation.cs create mode 100644 src/Ryujinx.Cpu/PrivateMemoryAllocator.cs create mode 100644 src/Ryujinx.Cpu/Ryujinx.Cpu.csproj create mode 100644 src/Ryujinx.Cpu/TickSource.cs create mode 100644 src/Ryujinx.Cpu/Tracking/CpuMultiRegionHandle.cs create mode 100644 src/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs create mode 100644 src/Ryujinx.Cpu/Tracking/CpuSmartMultiRegionHandle.cs create mode 100644 src/Ryujinx.Graphics.Device/DeviceState.cs create mode 100644 src/Ryujinx.Graphics.Device/IDeviceState.cs create mode 100644 src/Ryujinx.Graphics.Device/IDeviceStateWithContext.cs create mode 100644 src/Ryujinx.Graphics.Device/RwCallback.cs create mode 100644 src/Ryujinx.Graphics.Device/Ryujinx.Graphics.Device.csproj create mode 100644 src/Ryujinx.Graphics.Device/SizeCalculator.cs create mode 100644 src/Ryujinx.Graphics.GAL/AddressMode.cs create mode 100644 src/Ryujinx.Graphics.GAL/AdvancedBlendDescriptor.cs create mode 100644 src/Ryujinx.Graphics.GAL/AdvancedBlendOp.cs create mode 100644 src/Ryujinx.Graphics.GAL/AdvancedBlendOverlap.cs create mode 100644 src/Ryujinx.Graphics.GAL/AntiAliasing.cs create mode 100644 src/Ryujinx.Graphics.GAL/BlendDescriptor.cs create mode 100644 src/Ryujinx.Graphics.GAL/BlendFactor.cs create mode 100644 src/Ryujinx.Graphics.GAL/BlendOp.cs create mode 100644 src/Ryujinx.Graphics.GAL/BufferAssignment.cs create mode 100644 src/Ryujinx.Graphics.GAL/BufferHandle.cs create mode 100644 src/Ryujinx.Graphics.GAL/BufferRange.cs create mode 100644 src/Ryujinx.Graphics.GAL/Capabilities.cs create mode 100644 src/Ryujinx.Graphics.GAL/ColorF.cs create mode 100644 src/Ryujinx.Graphics.GAL/CompareMode.cs create mode 100644 src/Ryujinx.Graphics.GAL/CompareOp.cs create mode 100644 src/Ryujinx.Graphics.GAL/CounterType.cs create mode 100644 src/Ryujinx.Graphics.GAL/DepthMode.cs create mode 100644 src/Ryujinx.Graphics.GAL/DepthStencilMode.cs create mode 100644 src/Ryujinx.Graphics.GAL/DepthTestDescriptor.cs create mode 100644 src/Ryujinx.Graphics.GAL/DeviceInfo.cs create mode 100644 src/Ryujinx.Graphics.GAL/Extents2D.cs create mode 100644 src/Ryujinx.Graphics.GAL/Extents2DF.cs create mode 100644 src/Ryujinx.Graphics.GAL/Face.cs create mode 100644 src/Ryujinx.Graphics.GAL/Format.cs create mode 100644 src/Ryujinx.Graphics.GAL/FrontFace.cs create mode 100644 src/Ryujinx.Graphics.GAL/HardwareInfo.cs create mode 100644 src/Ryujinx.Graphics.GAL/ICounterEvent.cs create mode 100644 src/Ryujinx.Graphics.GAL/IPipeline.cs create mode 100644 src/Ryujinx.Graphics.GAL/IProgram.cs create mode 100644 src/Ryujinx.Graphics.GAL/IRenderer.cs create mode 100644 src/Ryujinx.Graphics.GAL/ISampler.cs create mode 100644 src/Ryujinx.Graphics.GAL/ITexture.cs create mode 100644 src/Ryujinx.Graphics.GAL/IWindow.cs create mode 100644 src/Ryujinx.Graphics.GAL/ImageCrop.cs create mode 100644 src/Ryujinx.Graphics.GAL/IndexType.cs create mode 100644 src/Ryujinx.Graphics.GAL/LogicalOp.cs create mode 100644 src/Ryujinx.Graphics.GAL/MagFilter.cs create mode 100644 src/Ryujinx.Graphics.GAL/MinFilter.cs create mode 100644 src/Ryujinx.Graphics.GAL/MultisampleDescriptor.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/BufferMap.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/BarrierCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/BeginTransformFeedbackCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferDisposeCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferGetDataCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Buffer/BufferSetDataCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearBufferCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetColorCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/ClearRenderTargetDepthStencilCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/CommandBufferBarrierCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/CopyBufferCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventDisposeCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/CounterEvent/CounterEventFlushCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/DispatchComputeCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedIndirectCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndexedIndirectCountCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndirectCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawIndirectCountCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/DrawTextureCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/EndHostConditionalRenderingCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/EndTransformFeedbackCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/IGALCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramCheckLinkCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramDisposeCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Program/ProgramGetBinaryCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ActionCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateBufferCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateProgramCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSamplerCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateSyncCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/CreateTextureCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/GetCapabilitiesCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/PreFrameCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ReportCounterCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/ResetCounterCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Renderer/UpdateCountersCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Sampler/SamplerDisposeCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetAlphaTestCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetBlendStateAdvancedCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetBlendStateCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthBiasCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthClampCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthModeCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetDepthTestCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFaceCullingCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetFrontFaceCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetImageCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetIndexBufferCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLineParametersCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetLogicOpStateCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetMultisampleStateCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPatchParametersCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPointParametersCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPolygonModeCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveRestartCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetPrimitiveTopologyCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetProgramCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRasterizerDiscardCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetColorMasksCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetScaleCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetScissorsCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStencilTestCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetStorageBuffersCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTextureAndSamplerCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetTransformFeedbackBuffersCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUniformBuffersCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetUserClipDistanceCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexAttribsCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetVertexBuffersCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetViewportsCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToScaledCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCopyToSliceCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureCreateViewCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureGetDataSliceCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureReleaseCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetStorageCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/TextureBarrierTiledCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/TryHostConditionalRenderingFlushCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/UpdateRenderScaleCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Commands/Window/WindowPresentCommand.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Model/CircularSpanPool.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Model/ResultBox.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Model/SpanRef.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Model/TableRef.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Resources/ProgramQueue.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/BinaryProgramRequest.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/IProgramRequest.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Resources/Programs/SourceProgramRequest.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedCounterEvent.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedProgram.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedSampler.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/SyncMap.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/ThreadedHelpers.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs create mode 100644 src/Ryujinx.Graphics.GAL/Multithreading/ThreadedWindow.cs create mode 100644 src/Ryujinx.Graphics.GAL/Origin.cs create mode 100644 src/Ryujinx.Graphics.GAL/PinnedSpan.cs create mode 100644 src/Ryujinx.Graphics.GAL/PolygonMode.cs create mode 100644 src/Ryujinx.Graphics.GAL/PolygonModeMask.cs create mode 100644 src/Ryujinx.Graphics.GAL/PrimitiveTopology.cs create mode 100644 src/Ryujinx.Graphics.GAL/ProgramLinkStatus.cs create mode 100644 src/Ryujinx.Graphics.GAL/ProgramPipelineState.cs create mode 100644 src/Ryujinx.Graphics.GAL/Rectangle.cs create mode 100644 src/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj create mode 100644 src/Ryujinx.Graphics.GAL/SamplerCreateInfo.cs create mode 100644 src/Ryujinx.Graphics.GAL/ScreenCaptureImageInfo.cs create mode 100644 src/Ryujinx.Graphics.GAL/ShaderBindings.cs create mode 100644 src/Ryujinx.Graphics.GAL/ShaderInfo.cs create mode 100644 src/Ryujinx.Graphics.GAL/ShaderSource.cs create mode 100644 src/Ryujinx.Graphics.GAL/StencilOp.cs create mode 100644 src/Ryujinx.Graphics.GAL/StencilTestDescriptor.cs create mode 100644 src/Ryujinx.Graphics.GAL/SupportBufferUpdater.cs create mode 100644 src/Ryujinx.Graphics.GAL/SwizzleComponent.cs create mode 100644 src/Ryujinx.Graphics.GAL/Target.cs create mode 100644 src/Ryujinx.Graphics.GAL/TextureCreateInfo.cs create mode 100644 src/Ryujinx.Graphics.GAL/TextureReleaseCallback.cs create mode 100644 src/Ryujinx.Graphics.GAL/UpscaleType.cs create mode 100644 src/Ryujinx.Graphics.GAL/VertexAttribDescriptor.cs create mode 100644 src/Ryujinx.Graphics.GAL/VertexBufferDescriptor.cs create mode 100644 src/Ryujinx.Graphics.GAL/Viewport.cs create mode 100644 src/Ryujinx.Graphics.GAL/ViewportSwizzle.cs create mode 100644 src/Ryujinx.Graphics.Gpu/ClassId.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Constants.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClass.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeClassState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Compute/ComputeQmd.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/ConditionalRenderEnabled.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/DeviceStateWithShadow.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClassState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaTexture.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/GPFifo/CompressedMethod.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPEntry.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClassState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoDevice.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClassState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/AluOperation.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/AluRegOperation.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/AssignmentOperation.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/IMacroEE.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/Macro.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLEFunctionName.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLETable.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/MacroInterpreter.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/MacroJit.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/MacroJitCompiler.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MME/MacroJitContext.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/MmeShadowScratch.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/SetMmeShadowRamControlMode.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/ShaderTexture.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendFunctions.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendManager.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendPreGenTable.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/AdvancedBlendUcode.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/Blender/UcodeAssembler.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/ConditionalRendering.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/ConstantBufferUpdater.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/DrawState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/IbStreamer.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/IndirectDrawType.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/RenderTargetUpdateFlags.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/SemaphoreUpdater.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/SpecializationStateUpdater.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClass.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodClassState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Twod/TwodTexture.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Types/Boolean32.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Types/ColorFormat.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Types/GpuVa.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Types/MemoryLayout.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Types/PrimitiveType.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Types/SamplerIndex.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Types/SbDescriptor.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Engine/Types/ZetaFormat.cs create mode 100644 src/Ryujinx.Graphics.Gpu/GpuChannel.cs create mode 100644 src/Ryujinx.Graphics.Gpu/GpuContext.cs create mode 100644 src/Ryujinx.Graphics.Gpu/GraphicsConfig.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/FormatInfo.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/ITextureDescriptor.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/Pool.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/PoolCache.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/Sampler.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/SamplerMinFilter.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/SamplerMipFilter.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/SamplerPoolCache.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/Texture.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureBindingsManager.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureComponent.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureDependency.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureInfo.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureMatchQuality.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureScaleMode.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureTarget.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Image/TextureViewCompatibility.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/BufferCacheEntry.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/BufferManager.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/BufferTextureBinding.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/CounterCache.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/IndexBuffer.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/MultiRangeWritableBlock.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/PteKind.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/ResourceKind.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/UnmapEventArgs.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/VertexBuffer.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/CachedShaderBindings.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/CachedShaderProgram.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/CachedShaderStage.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ComputeShaderCacheHashTable.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/BackgroundDiskCacheWriter.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/BinarySerializer.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/CompressionAlgorithm.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheCommon.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadException.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheLoadResult.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheOutputStreams.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/GuestCodeAndCbData.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/DiskCache/ShaderBinarySerializer.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/GpuAccessorState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/GpuChannelComputeState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/GpuChannelPoolState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/HashTable/HashState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/HashTable/IDataAccessor.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/HashTable/PartitionHashTable.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/HashTable/PartitionedHashTable.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/HashTable/SmartDataAccessor.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ResourceCounts.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ShaderAddresses.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ShaderCacheState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ShaderCodeAccessor.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ShaderDumpPaths.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ShaderDumper.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationList.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Shader/TransformFeedbackDescriptor.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Synchronization/SynchronizationManager.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Synchronization/Syncpoint.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Synchronization/SyncpointWaiterHandle.cs create mode 100644 src/Ryujinx.Graphics.Gpu/Window.cs create mode 100644 src/Ryujinx.Graphics.Host1x/ClassId.cs create mode 100644 src/Ryujinx.Graphics.Host1x/Devices.cs create mode 100644 src/Ryujinx.Graphics.Host1x/Host1xClass.cs create mode 100644 src/Ryujinx.Graphics.Host1x/Host1xClassRegisters.cs create mode 100644 src/Ryujinx.Graphics.Host1x/Host1xDevice.cs create mode 100644 src/Ryujinx.Graphics.Host1x/OpCode.cs create mode 100644 src/Ryujinx.Graphics.Host1x/Ryujinx.Graphics.Host1x.csproj create mode 100644 src/Ryujinx.Graphics.Host1x/SyncptIncrManager.cs create mode 100644 src/Ryujinx.Graphics.Host1x/ThiDevice.cs create mode 100644 src/Ryujinx.Graphics.Host1x/ThiRegisters.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/FFmpegContext.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/H264/Decoder.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/H264/H264BitStreamWriter.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/H264/SpsAndPpsReconstruction.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodec.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodec501.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodecContext.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVCodecID.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVFrame.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVLog.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVPacket.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/AVRational.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFCodec.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFCodecLegacy.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Native/FFmpegApi.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Ryujinx.Graphics.Nvdec.FFmpeg.csproj create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Surface.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.FFmpeg/Vp8/Decoder.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/BitDepth.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/CodecErr.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Common/BitUtils.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Common/MemoryAllocator.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Common/MemoryUtil.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Constants.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/DecodeFrame.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/DecodeMv.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Decoder.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Detokenize.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Convolve.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Filter.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/IntraPred.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/InvTxfm.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Prob.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/Reader.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Dsp/TxfmCommon.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Idct.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/InternalErrorException.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/InternalErrorInfo.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/LoopFilter.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Luts.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/PredCommon.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/QuantCommon.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/ReconInter.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/ReconIntra.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Ryujinx.Graphics.Nvdec.Vp9.csproj create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/TileBuffer.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/TileWorkerData.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/BModeInfo.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/BlockSize.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/Buf2D.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/FrameType.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/LoopFilter.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/LoopFilterInfoN.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/LoopFilterMask.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/LoopFilterThresh.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/MacroBlockD.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/MacroBlockDPlane.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/ModeInfo.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/MotionVectorContext.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/Mv.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/Mv32.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/MvClassType.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/MvJointType.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/MvRef.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/PartitionType.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/PlaneType.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/Position.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/PredictionMode.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/RefBuffer.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/ReferenceMode.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/ScaleFactors.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/SegLvlFeatures.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/Segmentation.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/Surface.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/TileInfo.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/TxMode.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/TxSize.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/TxType.cs create mode 100644 src/Ryujinx.Graphics.Nvdec.Vp9/Types/Vp9Common.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/ApplicationId.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/H264Decoder.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Image/SurfaceCache.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Image/SurfaceCommon.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Image/SurfaceReader.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Image/SurfaceWriter.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/MemoryExtensions.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/NvdecDecoderContext.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/NvdecDevice.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/NvdecRegisters.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/NvdecStatus.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/ResourceManager.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj create mode 100644 src/Ryujinx.Graphics.Nvdec/Types/H264/PictureInfo.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Types/H264/ReferenceFrame.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Types/Vp8/PictureInfo.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Types/Vp9/BackwardUpdates.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Types/Vp9/EntropyProbs.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Types/Vp9/FrameFlags.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Types/Vp9/FrameSize.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Types/Vp9/FrameStats.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Types/Vp9/LoopFilter.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Types/Vp9/PictureInfo.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Types/Vp9/Segmentation.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Vp8Decoder.cs create mode 100644 src/Ryujinx.Graphics.Nvdec/Vp9Decoder.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/BackgroundContextWorker.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Buffer.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Constants.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Debugger.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/DrawTextureEmulation.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/FsrScalingFilter.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/FxaaPostProcessingEffect.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/IPostProcessingEffect.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/IScalingFilter.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/ShaderHelper.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Shaders/ffx_a.h create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Shaders/ffx_fsr1.h create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_scaling.glsl create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_sharpening.glsl create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fxaa.glsl create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa.hlsl create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_blend.glsl create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_edge.glsl create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_neighbour.glsl create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/SmaaPostProcessingEffect.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaAreaTexture.bin create mode 100644 src/Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaSearchTexture.bin create mode 100644 src/Ryujinx.Graphics.OpenGL/EnumConversion.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/FormatInfo.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/FormatTable.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Framebuffer.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Handle.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Helper/GLXHelper.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Helper/WGLHelper.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/HwCapabilities.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/IOpenGLContext.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/FormatConverter.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/ITextureInfo.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/IntermmediatePool.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/Sampler.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/TextureBase.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/TextureCopyIncompatible.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/TextureCopyMS.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/TextureStorage.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Image/TextureView.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/OpenGLRenderer.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/PersistentBuffers.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Pipeline.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Program.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Queries/BufferedQuery.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Queries/CounterQueue.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Queries/CounterQueueEvent.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Queries/Counters.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/ResourcePool.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj create mode 100644 src/Ryujinx.Graphics.OpenGL/Sync.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/VertexArray.cs create mode 100644 src/Ryujinx.Graphics.OpenGL/Window.cs create mode 100644 src/Ryujinx.Graphics.Shader/AlphaTestOp.cs create mode 100644 src/Ryujinx.Graphics.Shader/AttributeType.cs create mode 100644 src/Ryujinx.Graphics.Shader/BufferDescriptor.cs create mode 100644 src/Ryujinx.Graphics.Shader/BufferUsageFlags.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/CodeGenContext.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Declarations.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/DefaultNames.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/GlslGenerator.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/AtomicMinMaxS32Shared.glsl create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/AtomicMinMaxS32Storage.glsl create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/HelperFunctionNames.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighU32.glsl create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/Shuffle.glsl create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleDown.glsl create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleUp.glsl create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/ShuffleXor.glsl create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/StoreSharedSmallInt.glsl create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/StoreStorageSmallInt.glsl create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_cp.glsl create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_fp.glsl create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_vp.glsl create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGen.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenBallot.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenCall.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenFSI.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenHelper.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenMemory.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenPacking.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstGenVector.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstInfo.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/InstType.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/Instructions/IoMap.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/NumberFormatter.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/OperandManager.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Glsl/TypeConversion.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Spirv/CodeGenContext.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Declarations.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Spirv/EnumConversion.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Spirv/Instructions.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Spirv/IoMap.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Spirv/OperationResult.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Spirv/ScalingHelpers.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvDelegates.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Spirv/SpirvGenerator.cs create mode 100644 src/Ryujinx.Graphics.Shader/CodeGen/Spirv/TextureMeta.cs create mode 100644 src/Ryujinx.Graphics.Shader/Constants.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/Block.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/DecodedFunction.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/DecodedProgram.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/Decoder.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/FunctionType.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/InstDecoders.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/InstName.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/InstOp.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/InstProps.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/InstTable.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/Register.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/RegisterConsts.cs create mode 100644 src/Ryujinx.Graphics.Shader/Decoders/RegisterType.cs create mode 100644 src/Ryujinx.Graphics.Shader/IGpuAccessor.cs create mode 100644 src/Ryujinx.Graphics.Shader/InputTopology.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/AttributeMap.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmit.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitAluHelper.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitAttribute.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitBarrier.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitBitfield.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitConditionCode.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitConversion.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitFloatArithmetic.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitFloatComparison.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitFloatMinMax.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitFlowControl.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitHelper.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitIntegerArithmetic.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitIntegerComparison.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitIntegerLogical.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitIntegerMinMax.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitMemory.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitMove.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitMultifunction.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitNop.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitPredicate.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitShift.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitSurface.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitVideoArithmetic.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitVideoMinMax.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitWarp.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/InstEmitter.cs create mode 100644 src/Ryujinx.Graphics.Shader/Instructions/Lop3Expression.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/BasicBlock.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/CommentNode.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Function.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/INode.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Instruction.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/IoVariable.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/IrConsts.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operand.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandHelper.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/OperandType.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/Operation.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/PhiNode.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/StorageKind.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureFlags.cs create mode 100644 src/Ryujinx.Graphics.Shader/IntermediateRepresentation/TextureOperation.cs create mode 100644 src/Ryujinx.Graphics.Shader/OutputTopology.cs create mode 100644 src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj create mode 100644 src/Ryujinx.Graphics.Shader/SamplerType.cs create mode 100644 src/Ryujinx.Graphics.Shader/ShaderIdentification.cs create mode 100644 src/Ryujinx.Graphics.Shader/ShaderProgram.cs create mode 100644 src/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs create mode 100644 src/Ryujinx.Graphics.Shader/ShaderStage.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/AstAssignment.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/AstBlock.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/AstBlockType.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/AstBlockVisitor.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/AstComment.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/AstHelper.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/AstNode.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/AstOperand.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/AstOperation.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/AstOptimizer.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/AstTextureOperation.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/GotoElimination.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/GotoStatement.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/HelperFunctionsMask.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/IAstNode.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/InstructionInfo.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/IoDefinition.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/OperandInfo.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/PhiFunctions.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/StructuredFunction.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgram.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramContext.cs create mode 100644 src/Ryujinx.Graphics.Shader/StructuredIr/StructuredProgramInfo.cs create mode 100644 src/Ryujinx.Graphics.Shader/SupportBuffer.cs create mode 100644 src/Ryujinx.Graphics.Shader/TessPatchType.cs create mode 100644 src/Ryujinx.Graphics.Shader/TessSpacing.cs create mode 100644 src/Ryujinx.Graphics.Shader/TextureDescriptor.cs create mode 100644 src/Ryujinx.Graphics.Shader/TextureFormat.cs create mode 100644 src/Ryujinx.Graphics.Shader/TextureHandle.cs create mode 100644 src/Ryujinx.Graphics.Shader/TextureUsageFlags.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/AggregateType.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/AttributeConsts.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/ControlFlowGraph.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Dominance.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/EmitterContext.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/EmitterContextInsts.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/FeatureFlags.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/FunctionMatch.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/GlobalMemory.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessToIndexed.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Optimizations/BranchElimination.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Optimizations/ConstantFolding.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Optimizations/GlobalToStorage.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Optimizations/Optimizer.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Optimizations/Simplification.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Optimizations/Utils.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/RegisterUsage.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Rewriter.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/ShaderHeader.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/ShaderIdentifier.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Ssa.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/TargetApi.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/TargetLanguage.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/TranslationFlags.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/TranslationOptions.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/Translator.cs create mode 100644 src/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs create mode 100644 src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs create mode 100644 src/Ryujinx.Graphics.Texture/Astc/AstcDecoderException.cs create mode 100644 src/Ryujinx.Graphics.Texture/Astc/AstcPixel.cs create mode 100644 src/Ryujinx.Graphics.Texture/Astc/BitStream128.cs create mode 100644 src/Ryujinx.Graphics.Texture/Astc/Bits.cs create mode 100644 src/Ryujinx.Graphics.Texture/Astc/EndPointSet.cs create mode 100644 src/Ryujinx.Graphics.Texture/Astc/IntegerEncoded.cs create mode 100644 src/Ryujinx.Graphics.Texture/Astc/IntegerSequence.cs create mode 100644 src/Ryujinx.Graphics.Texture/BC6Decoder.cs create mode 100644 src/Ryujinx.Graphics.Texture/BC7Decoder.cs create mode 100644 src/Ryujinx.Graphics.Texture/BCnDecoder.cs create mode 100644 src/Ryujinx.Graphics.Texture/BCnEncoder.cs create mode 100644 src/Ryujinx.Graphics.Texture/BlockLinearConstants.cs create mode 100644 src/Ryujinx.Graphics.Texture/BlockLinearLayout.cs create mode 100644 src/Ryujinx.Graphics.Texture/Bpp12Pixel.cs create mode 100644 src/Ryujinx.Graphics.Texture/ETC2Decoder.cs create mode 100644 src/Ryujinx.Graphics.Texture/Encoders/BC7Encoder.cs create mode 100644 src/Ryujinx.Graphics.Texture/Encoders/EncodeMode.cs create mode 100644 src/Ryujinx.Graphics.Texture/LayoutConverter.cs create mode 100644 src/Ryujinx.Graphics.Texture/OffsetCalculator.cs create mode 100644 src/Ryujinx.Graphics.Texture/PixelConverter.cs create mode 100644 src/Ryujinx.Graphics.Texture/Region.cs create mode 100644 src/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj create mode 100644 src/Ryujinx.Graphics.Texture/Size.cs create mode 100644 src/Ryujinx.Graphics.Texture/SizeCalculator.cs create mode 100644 src/Ryujinx.Graphics.Texture/SizeInfo.cs create mode 100644 src/Ryujinx.Graphics.Texture/Utils/BC67Tables.cs create mode 100644 src/Ryujinx.Graphics.Texture/Utils/BC67Utils.cs create mode 100644 src/Ryujinx.Graphics.Texture/Utils/BC7ModeInfo.cs create mode 100644 src/Ryujinx.Graphics.Texture/Utils/Block.cs create mode 100644 src/Ryujinx.Graphics.Texture/Utils/RgbaColor32.cs create mode 100644 src/Ryujinx.Graphics.Texture/Utils/RgbaColor8.cs create mode 100644 src/Ryujinx.Graphics.Vic/Blender.cs create mode 100644 src/Ryujinx.Graphics.Vic/Image/BufferPool.cs create mode 100644 src/Ryujinx.Graphics.Vic/Image/InputSurface.cs create mode 100644 src/Ryujinx.Graphics.Vic/Image/Pixel.cs create mode 100644 src/Ryujinx.Graphics.Vic/Image/Surface.cs create mode 100644 src/Ryujinx.Graphics.Vic/Image/SurfaceCommon.cs create mode 100644 src/Ryujinx.Graphics.Vic/Image/SurfaceReader.cs create mode 100644 src/Ryujinx.Graphics.Vic/Image/SurfaceWriter.cs create mode 100644 src/Ryujinx.Graphics.Vic/Rectangle.cs create mode 100644 src/Ryujinx.Graphics.Vic/ResourceManager.cs create mode 100644 src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj create mode 100644 src/Ryujinx.Graphics.Vic/Scaler.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/BlendingSlotStruct.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/ClearRectStruct.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/ConfigStruct.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/DeinterlaceMode.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/FrameFormat.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/LumaKeyStruct.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/MatrixStruct.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/OutputConfig.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/OutputSurfaceConfig.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/PipeConfig.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/PixelFormat.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/SlotConfig.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/SlotStruct.cs create mode 100644 src/Ryujinx.Graphics.Vic/Types/SlotSurfaceConfig.cs create mode 100644 src/Ryujinx.Graphics.Vic/VicDevice.cs create mode 100644 src/Ryujinx.Graphics.Vic/VicRegisters.cs create mode 100644 src/Ryujinx.Graphics.Video/FrameField.cs create mode 100644 src/Ryujinx.Graphics.Video/H264PictureInfo.cs create mode 100644 src/Ryujinx.Graphics.Video/IDecoder.cs create mode 100644 src/Ryujinx.Graphics.Video/IH264Decoder.cs create mode 100644 src/Ryujinx.Graphics.Video/ISurface.cs create mode 100644 src/Ryujinx.Graphics.Video/IVp9Decoder.cs create mode 100644 src/Ryujinx.Graphics.Video/Plane.cs create mode 100644 src/Ryujinx.Graphics.Video/Ryujinx.Graphics.Video.csproj create mode 100644 src/Ryujinx.Graphics.Video/Vp8PictureInfo.cs create mode 100644 src/Ryujinx.Graphics.Video/Vp9BackwardUpdates.cs create mode 100644 src/Ryujinx.Graphics.Video/Vp9EntropyProbs.cs create mode 100644 src/Ryujinx.Graphics.Video/Vp9Mv.cs create mode 100644 src/Ryujinx.Graphics.Video/Vp9MvRef.cs create mode 100644 src/Ryujinx.Graphics.Video/Vp9PictureInfo.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Auto.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/AutoFlushCounter.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/BackgroundResources.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/BitMap.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/BufferAllocationType.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/BufferHolder.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/BufferManager.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/BufferState.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/BufferUsageBitmap.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/CacheByRange.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/CommandBufferScoped.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Constants.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DescriptorSetCollection.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DisposableBuffer.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DisposableBufferView.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DisposableFramebuffer.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DisposableImage.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DisposableImageView.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DisposableMemory.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DisposablePipeline.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DisposableRenderPass.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/DisposableSampler.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/IPostProcessingEffect.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/IScalingFilter.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.glsl create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpening.glsl create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpening.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/Fxaa.glsl create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/Fxaa.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaBlend.glsl create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaBlend.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaEdge.glsl create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaEdge.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaNeighbour.glsl create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaNeighbour.spv create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/SmaaConstants.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/SmaaPostProcessingEffect.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaAreaTexture.bin create mode 100644 src/Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaSearchTexture.bin create mode 100644 src/Ryujinx.Graphics.Vulkan/EnumConversion.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/FenceHelper.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/FenceHolder.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/FormatCapabilities.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/FormatConverter.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/FormatTable.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/HashTableSlim.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/HelperShader.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/IdList.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/IndexBufferPattern.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/IndexBufferState.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/MemoryAllocation.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/MemoryAllocator.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/MemoryAllocatorBlockList.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/MoltenVK/MVKConfiguration.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/MoltenVK/MVKInitialization.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/NativeArray.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PersistentFlushBuffer.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PipelineBase.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PipelineConverter.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PipelineDynamicState.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PipelineFull.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PipelineLayoutCache.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PipelineState.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/PipelineUid.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Queries/BufferedQuery.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Queries/CounterQueue.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Queries/CounterQueueEvent.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Queries/Counters.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj create mode 100644 src/Ryujinx.Graphics.Vulkan/SamplerHolder.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/SemaphoreHolder.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Shader.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ChangeBufferStrideShaderSource.comp create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorBlitClearAlphaFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorBlitFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorBlitMsFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorBlitVertexShaderSource.vert create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorClearFFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorClearSIFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorClearUIFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorClearVertexShaderSource.vert create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorCopyShorteningComputeShaderSource.comp create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorCopyToNonMsComputeShaderSource.comp create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorCopyWideningComputeShaderSource.comp create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorDrawToMsFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ColorDrawToMsVertexShaderSource.vert create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ConvertIndexBufferShaderSource.comp create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ConvertIndirectDataShaderSource.comp create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/DepthBlitFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/DepthBlitMsFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/DepthDrawToMsFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/DepthDrawToNonMsFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/ShaderBinaries.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/StencilBlitFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/StencilBlitMsFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/StencilDrawToMsFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/Shaders/StencilDrawToNonMsFragmentShaderSource.frag create mode 100644 src/Ryujinx.Graphics.Vulkan/SpecInfo.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/StagingBuffer.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/SyncManager.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/TextureBuffer.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/TextureCopy.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/TextureStorage.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/TextureView.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Vendor.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/VertexBufferState.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/VulkanConfiguration.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/VulkanDebugMessenger.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/VulkanException.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/VulkanInstance.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/VulkanPhysicalDevice.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/Window.cs create mode 100644 src/Ryujinx.Graphics.Vulkan/WindowBase.cs create mode 100644 src/Ryujinx.HLE/AssemblyInfo.cs create mode 100644 src/Ryujinx.HLE/Exceptions/GuestBrokeExecutionException.cs create mode 100644 src/Ryujinx.HLE/Exceptions/InternalServiceException.cs create mode 100644 src/Ryujinx.HLE/Exceptions/InvalidFirmwarePackageException.cs create mode 100644 src/Ryujinx.HLE/Exceptions/InvalidNpdmException.cs create mode 100644 src/Ryujinx.HLE/Exceptions/InvalidStructLayoutException.cs create mode 100644 src/Ryujinx.HLE/Exceptions/InvalidSystemResourceException.cs create mode 100644 src/Ryujinx.HLE/Exceptions/ServiceNotImplementedException.cs create mode 100644 src/Ryujinx.HLE/Exceptions/TamperCompilationException.cs create mode 100644 src/Ryujinx.HLE/Exceptions/TamperExecutionException.cs create mode 100644 src/Ryujinx.HLE/Exceptions/UndefinedInstructionException.cs create mode 100644 src/Ryujinx.HLE/FileSystem/ContentManager.cs create mode 100644 src/Ryujinx.HLE/FileSystem/ContentPath.cs create mode 100644 src/Ryujinx.HLE/FileSystem/EncryptedFileSystemCreator.cs create mode 100644 src/Ryujinx.HLE/FileSystem/LocationEntry.cs create mode 100644 src/Ryujinx.HLE/FileSystem/SystemVersion.cs create mode 100644 src/Ryujinx.HLE/FileSystem/VirtualFileSystem.cs create mode 100644 src/Ryujinx.HLE/HLEConfiguration.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/AppletManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/BootDisplayKind.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/BrowserArgument.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutput.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutputType.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/DocumentKind.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/LeftStickMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/ShimKind.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/WebArgHeader.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLV.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLVType.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/WebCommonReturnValue.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Browser/WebExitReason.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/CommonArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Controller/ControllerAppletUiArgs.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgHeader.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgPrivate.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgV7.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportArgVPre7.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Controller/ControllerSupportResultInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Error/ApplicationErrorArg.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Error/ErrorApplet.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonArg.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Error/ErrorCommonHeader.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Error/ErrorType.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/IApplet.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectResult.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InitialCursorPosition.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardRequest.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardResponse.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineKeyboardState.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InlineResponses.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InputFormMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidButtonFlags.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/InvalidCharFlags.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardCalcFlags.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardInputMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMiniaturizationMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/KeyboardResult.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/PasswordMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnA.png create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnA.svg create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnB.png create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_BtnB.svg create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_KeyF6.png create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Icon_KeyF6.svg create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/Resources/Logo_Ryujinx.png create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardAppear.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardAppearEx.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCalc.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCalcEx.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardCustomizeDic.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardDictSet.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardInitialize.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRenderer.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardRendererBase.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiArgs.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUiState.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardUserWord.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TRef.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/TimedAction.cs create mode 100644 src/Ryujinx.HLE/HOS/ArmProcessContext.cs create mode 100644 src/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArraySubscriptingExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ArrayType.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BaseNode.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BinaryExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/BracedRangeExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CallExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CastExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConditionalExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ConversionOperatorType.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorDtorNameType.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/CtorVtableSpecialName.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DeleteExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DtorName.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/DynamicExceptionSpec.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ElaboratedType.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EnclosedExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/EncodedFunction.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FoldExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ForwardTemplateReference.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionParameter.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/FunctionType.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/GlobalQualifiedName.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/InitListExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerCastExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/IntegerLiteral.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LiteralOperator.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/LocalName.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/MemberExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameType.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NameTypeWithTemplateArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NestedName.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NewExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NodeArray.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/NoexceptSpec.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameter.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PackedTemplateParameterExpansion.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ParentNode.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PointerType.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PostfixQualifiedType.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/PrefixExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/QualifiedName.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/Qualifier.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ReferenceType.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialName.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/SpecialSubstitution.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/StdQualifiedName.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/TemplateArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Ast/ThrowExpression.cs create mode 100644 src/Ryujinx.HLE/HOS/Diagnostics/Demangler/Demangler.cs create mode 100644 src/Ryujinx.HLE/HOS/HomebrewRomFsStream.cs create mode 100644 src/Ryujinx.HLE/HOS/Horizon.cs create mode 100644 src/Ryujinx.HLE/HOS/IdDictionary.cs create mode 100644 src/Ryujinx.HLE/HOS/Ipc/IpcBuffDesc.cs create mode 100644 src/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs create mode 100644 src/Ryujinx.HLE/HOS/Ipc/IpcMagic.cs create mode 100644 src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs create mode 100644 src/Ryujinx.HLE/HOS/Ipc/IpcMessageType.cs create mode 100644 src/Ryujinx.HLE/HOS/Ipc/IpcPtrBuffDesc.cs create mode 100644 src/Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs create mode 100644 src/Ryujinx.HLE/HOS/Ipc/ServiceProcessRequest.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/IKFutureSchedulerObject.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/KResourceLimit.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/KSynchronizationObject.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/KSystemControl.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/KernelInit.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/LimitableResource.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/MemoryArrange.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/MemroySize.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Common/MersenneTwister.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/ChannelState.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptor.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightClientSession.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightServerSession.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KLightSession.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KPort.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerPort.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/KernelConstants.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/KernelContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/KernelStatic.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/AddressSpaceType.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/DramMemoryMap.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KCodeMemory.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlock.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryBlockSlabManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryRegionManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KPageBitmap.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KPageHeap.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KPageList.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KPageNode.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KScopedPageList.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KSharedMemory.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KSlabHeap.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryFillValue.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryPermission.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryRegion.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryState.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Memory/SharedMemoryStorage.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityExtensions.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/CapabilityType.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/HleProcessDebugger.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/IProcessContextFactory.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/KContextIdManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/KHandleEntry.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/KProcessCapabilities.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/KTlsPageManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/ProcessContextFactory.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationFlags.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/ProcessCreationInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/ProcessExecutionContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/ProcessState.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Process/ProcessTamperInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/CodeMemoryOperation.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/InfoType.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/MemoryInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/PointerSizedAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcImplAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallHandler.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ThreadContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/ArbitrationType.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/KEvent.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/KPriorityQueue.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/KReadableEvent.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/KThreadContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/KWritableEvent.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/SignalType.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadSchedState.cs create mode 100644 src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs create mode 100644 src/Ryujinx.HLE/HOS/LibHacHorizonManager.cs create mode 100644 src/Ryujinx.HLE/HOS/ModLoader.cs create mode 100644 src/Ryujinx.HLE/HOS/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/ServiceCtx.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountSaveDataManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IManagerForApplication.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IManagerForSystemService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IProfile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IProfileEditor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ManagerServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/ProfileServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/ApplicationServiceServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/AsyncContext/AsyncExecution.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/DefaultUserImage.jpg create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForAdministrator.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForApplication.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForSystemService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/IAsyncContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/IAsyncNetworkServiceLicenseKindContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/IBaasAccessTokenAccessor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/ProfilesJsonSerializerContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountServiceFlag.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/NetworkServiceLicenseKind.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/ProfilesJson.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserId.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/UserProfileJson.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/Dauth/IService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Account/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ILibraryAppletProxy.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ISystemAppletProxy.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/AppletStandalone.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/IProcessWindingController.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAppletCommonFunctions.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IApplicationCreator.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAudioController.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDebugFunctions.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDisplayController.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IGlobalStateController.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IHomeMenuFunctions.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IWindowController.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AlbumReportOption.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AppletMessage.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/FocusState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/OperationMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/WirelessPriorityMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletFifo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAllSystemAppletProxiesService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorage.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorageAccessor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletId.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletIdentityInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletProcessLaunchReason.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/LaunchParameterKind.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/ProgramSpecifyKind.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/IApplicationProxy.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletOE/IApplicationProxyService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/Idle/IPolicyManagerSystem.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/Omm/IOperationModeManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/Spsm/IPowerStateInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/Tcap/IManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/IManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/IManagerPrivileged.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/ISession.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/ISystemManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/ManagerServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/PerformanceState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/SessionServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/SystemManagerServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/Types/CpuBoostMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceConfiguration.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Apm/Types/PerformanceMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Arp/ApplicationLaunchProperty.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Arp/IReader.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Arp/IWriter.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/Decoder.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/DecoderCommon.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IDecoder.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/MultiSampleDecoder.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusDecoderFlags.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParameters.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParametersEx.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusParametersEx.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Bcat/IServiceCreator.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Bcat/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IBcatService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheDirectoryService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheFileService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheProgressService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheStorageService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/Types/DeliveryCacheProgressImpl.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Bgtc/IStateControlService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Bgtc/ITaskService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Bluetooth/BluetoothDriver/BluetoothEventManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothDriver.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothUser.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/BluetoothManager/BtmUser/IBtmUserCore.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtm.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmDebug.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmSystem.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmUser.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/BluetoothManager/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/CaptureManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/IAlbumAccessorService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/IAlbumApplicationService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/IAlbumControlService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotApplicationService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/IScreenShotControlService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/IScreenshotService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumFileDateTime.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumImageOrientation.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/Types/AlbumStorage.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/Types/ApplicationAlbumEntry.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/Types/ContentType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Caps/Types/ScreenShotAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Cec/ICecManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/CommandCmifAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/CommandTIpcAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/DisposableIpcService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/DummyService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ectx/IReaderForSystem.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ectx/IWriterForApplication.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ectx/IWriterForSystem.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Erpt/IContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Erpt/ISession.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Es/IETicketService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Eupld/IControl.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Eupld/IRequest.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fatal/IPrivateService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fatal/IService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext32.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fatal/Types/CpuContext64.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fatal/Types/FatalPolicy.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/Friend.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/FriendFilter.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatus.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatusFilter.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/UserPresence.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IDaemonSuspendSessionService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/NotificationEventHandler.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationEventType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/FriendServicePermissionLevel.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/PlayHistoryRegistrationKey.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IDirectory.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IFileSystem.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/IStorage.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/IDeviceOperator.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxyForLoader.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/IMultiCommitManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/IProgramRegistry.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/ISaveDataInfoReader.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Fs/Types/FileSystemType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Grc/IGrcService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Grc/IRemoteVideoTransfer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Hid.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/BaseDevice.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/DebugPadDevice.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/KeyboardDevice.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/MouseDevice.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/TouchDevice.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/ControllerConfig.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/GamepadInput.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/JoystickPosition.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/KeyboardInput.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/SixAxisInput.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/TouchPoint.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/HidUtils.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IActiveVibrationDeviceList.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IAppletResource.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadHandheldActivationMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadJoyDeviceType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/AccelerometerParameters.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/GyroscopeZeroDriftMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/SensorFusionParameters.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceHandle.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDevicePosition.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceValue.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationValue.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/IHidDebugServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/IHidSystemServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/IHidbusServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/ISystemServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorSystemServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Irs/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/ImageTransferProcessorState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/IrCameraHandle.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedClusteringProcessorConfig.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedImageTransferProcessorConfig.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedMomentProcessorConfig.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedTeraPluginProcessorConfig.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/AppletFooterUiType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/HidVector.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerKeys.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadColor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadIdType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/PlayerIndex.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/NpadJoyHoldType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AnalogStickState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AtomicStorage.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/ISampledDataStruct.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/RingLifo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadButton.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKey.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKeyShift.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardModifier.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseButton.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/DeviceType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadBatteryLevel.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadButton.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadColorAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadCommonState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadFullKeyColorState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadGcTriggerState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadInternalState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyAssignmentMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyColorState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLarkType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLuciaType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadStyleTag.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemButtonProperties.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemProperties.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/SharedMemory.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchScreenState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ins/IReceiverManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ins/ISenderManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/IpcService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Lbl/ILblController.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Lbl/LblControllerServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/IMonitorServiceCreator.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/ISystemServiceCreator.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/IUserServiceCreator.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/IServiceCreator.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/NetworkInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/Types/NetworkState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ldn/UserServiceCreator/IUserLocalCommunicationService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Loader/IDebugMonitorInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Loader/IProcessManagerInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Loader/IShellInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Loader/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mig/IService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/DatabaseImpl.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/DatabaseSessionMetadata.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Helper.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/IImageDatabaseService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/IStaticService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/MiiDatabaseManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/StaticService/DatabaseServiceImpl.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/StaticService/IDatabaseService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/Age.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/BeardType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/CharInfoElement.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/CommonColor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/CoreData.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/CreateId.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/DefaultMii.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/EyeType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/EyebrowType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineColor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineMake.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/FacelineWrinkle.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/FontRegion.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/Gender.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/GlassType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/HairFlip.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/HairType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/IElement.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/IStoredData.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/MoleType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/MouthType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/MustacheType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/Nickname.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/NintendoFigurineDatabase.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/NoseType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/Race.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/RandomMiiConstants.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/Source.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/SourceFlag.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/SpecialMiiKeyCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/StoreData.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/StoreDataElement.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/Types/Ver3StoreData.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mii/UtilityImpl.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mm/IRequest.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mm/Types/MultiMediaOperationType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mm/Types/MultiMediaSession.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mnpp/IServiceForApplication.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Mnpp/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ncm/IContentManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ncm/Lr/ILocationResolverManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ncm/Lr/LocationResolverManager/ILocationResolver.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ncm/Lr/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/News/IServiceCreator.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/IAmManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/ISystemManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/IUserManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/IUserManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/INfc.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/NfcPermissionLevel.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/State.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/AmiiboJsonSerializerContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IDebugManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ISystemManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IUserManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/INfp.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/AmiiboConstants.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/CommonInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/DeviceType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/ModelInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/MountTarget.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDevice.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDeviceState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpPermissionLevel.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/RegisterInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/State.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/TagInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/VirtualAmiiboFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ngct/IService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ngct/IServiceWithManagementApi.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ngct/NgctServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/IStaticService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/GeneralServiceManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/GeneralService/Types/GeneralServiceDetail.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IGeneralService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IRequest.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/DnsSetting.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionStatus.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/InternetConnectionType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpAddressSetting.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpSettingData.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/IpV4Address.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/NetworkProfileData.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/ProxySetting.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/Types/WirelessSettingData.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nim/INetworkInstallManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessServerInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessSystemInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAccessor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceAsync.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nim/IShopServiceManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nim/Ntc/IStaticService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nim/Ntc/StaticService/IEnsureNetworkClockAvailabilityService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nim/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForApplication.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForSystem.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Npns/INpnsSystem.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Npns/INpnsUser.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IAddOnContentManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IContentsServiceManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ns/Aoc/IPurchaseEventManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ns/Aoc/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ns/IDevelopInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ns/ISystemUpdateInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ns/IVulnerabilityManagerInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/Host1xContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/INvDrvDebugFSServices.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/INvGemControl.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/INvGemCoreDump.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvDeviceFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/NvHostAsGpuDeviceFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AddressSpaceFlags.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/AllocSpaceArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/BindChannelArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/FreeSpaceArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/GetVaRegionsArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/InitializeExArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/MapBufferExArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/RemapArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostAsGpu/Types/UnmapBufferArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/ChannelInitialization.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostChannelDeviceFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/NvHostGpuDeviceFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocGpfifoExArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/AllocObjCtxArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/GetParameterArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/MapCommandBufferArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannel.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/NvChannelPriority.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SetErrorNotifierArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/SubmitGpfifoFlags.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostChannel/Types/ZcullBindArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/NvHostCtrlDeviceFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/EventWaitArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/GetConfigurationArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEvent.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostEventState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/SyncptWaitExArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/NvHostCtrlGpuDeviceFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetActiveSlotMaskArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetCharacteristicsArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetGpuTimeArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/GetTpcMasksArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZbcSetTableArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetCtxSizeArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrlGpu/Types/ZcullGetInfoArguments.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostDbgGpu/NvHostDbgGpuDeviceFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostProfGpu/NvHostProfGpuDeviceFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvInternalResult.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/NvMapDeviceFile.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapAlloc.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapCreate.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFree.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapFromId.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapGetId.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandle.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapHandleParam.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapIdDictionary.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvMap/Types/NvMapParam.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvIoctl.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/NvMemoryAllocator.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/Types/NvFence.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/Types/NvIoctlNotImplementedException.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/Types/NvQueryEventNotImplementedException.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/Types/NvResult.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Nv/Types/NvStatus.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForApplication.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForSystemService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Olsc/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ovln/IReceiverService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ovln/ISenderService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcie/ILogManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcie/IManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pctl/IParentalControlServiceFactory.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pctl/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IBoardPowerControlManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcv/Bpc/IRtcManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/ClkrstManager/IClkrstSession.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IArbitrationManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcv/Clkrst/IClkrstManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcv/IPcvService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcv/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcv/Rgltr/IRegulatorManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcv/Rtc/IRtcManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pcv/Types/DeviceCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pm/IBootModeInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pm/IDebugMonitorInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pm/IInformationInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pm/IShellInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Pm/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Psc/IPmControl.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Psc/IPmService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Psc/IPmUnknown.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ptm/Fan/IManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ptm/Fgm/IDebugger.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ptm/Fgm/ISession.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ptm/Pcm/IManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ptm/Psm/IPsmSession.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ptm/Psm/Types/ChargerType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ptm/Tc/IManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ptm/Ts/IMeasurementServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ptm/Ts/Types/Location.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ro/IRoInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ro/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ro/Types/NRRCertification.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ro/Types/NroInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ro/Types/NrrHeader.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ro/Types/NrrInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sdb/Avm/IAvmService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/INotifyService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/IQueryService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/QueryPlayStatisticsManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/ApplicationPlayStatistics.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/QueryService/Types/PlayLogQueryCapability.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sdb/Pdm/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sdb/Pl/ISharedFontManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sdb/Pl/SharedFontManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sdb/Pl/Types/SharedFontType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/ServerBase.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/ServiceAttributes.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Settings/IFactorySettingsServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Settings/IFirmwareDebugSettingsServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Settings/ISettingsServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Settings/KeyCodeMaps.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Settings/NxSettings.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Settings/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Settings/Types/PlatformRegion.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sm/IManagerInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sm/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sm/SmRegistry.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/BsdContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IClient.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/IFileDescriptor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ISocket.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/EventFileDescriptorPollManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocket.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/ManagedSocketPollManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WSAError.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Impl/WinSockHelper.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/ServerInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdAddressFamily.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdIoctl.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMMsgHdr.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMsgHdr.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSockAddr.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketCreationFlags.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketFlags.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketOption.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketShutdownFlags.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdSocketType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/EventFdFlags.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/IPollManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/LinuxError.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEvent.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventData.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/PollEventTypeMask.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/TimeVal.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Ethc/IEthInterfaceGroup.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/IManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Manager/FqdnResolver.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/ApplicationServerEnvironmentType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Nsd/Types/NsdSettings.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsBlacklist.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Proxy/DnsMitmResolver.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfo4.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerialized.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerializedHeader.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/GaiError.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/NetDBError.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/SfdnsresContants.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Spl/IGeneralInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Spl/IRandomInterface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Spl/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Spl/Types/ConfigItem.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Spl/Types/DramId.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Spl/Types/HardwareState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Spl/Types/HardwareType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Spl/Types/SmcResult.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Srepo/ISrepoService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/BuiltInCertificateManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/ISslService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslConnection.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslConnectionBase.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/SslService/ISslContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/SslService/SslManagedSocketConnection.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/Types/BuiltInCertificateInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/Types/CaCertificateId.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/Types/CertificateFormat.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/Types/IoMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/Types/OptionType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/Types/SessionCacheMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/Types/SslVersion.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/Types/TrustedCertStatus.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Ssl/Types/VerifyOption.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferItemConsumer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueue.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueConsumer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueProducer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlot.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferSlotArray.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ConsumerBase.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/HOSBinderDriverServer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IBinder.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IConsumerListener.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IFlattenable.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IGraphicBufferProducer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IProducerListener.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/LayerState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowApi.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowAttribute.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowScalingMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/NativeWindowTransform.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Parcel.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/ParcelHeader.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/PixelFormat.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Status.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/SurfaceFlinger.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidFence.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/AndroidStrongPointer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferItem.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/BufferState.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorBytePerPixel.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorComponent.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorDataType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorFormat.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorShift.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSpace.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Color/ColorSwizzle.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBuffer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/GraphicBufferHeader.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBuffer.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurface.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/NvGraphicBufferSurfaceArray.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/SurfaceFlinger/Types/Rect.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockContextWriter.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/EphemeralNetworkSystemClockCore.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/LocalSystemClockContextWriter.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/NetworkSystemClockContextWriter.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardLocalSystemClockCore.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardNetworkSystemClockCore.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardSteadyClockCore.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/StandardUserSystemClockCore.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/SteadyClockCore.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockContextUpdateCallback.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/SystemClockCore.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/TickBasedSteadyClockCore.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/ClockSnapshot.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SteadyClockTimePoint.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/SystemClockContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Clock/Types/TimeSpanType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/IAlarmService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/IPowerStateRequestHandler.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForGlue.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/IStaticServiceForPsc.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/ITimeServiceManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/StaticService/ISteadyClock.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/StaticService/ISystemClock.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForGlue.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/StaticService/ITimeZoneServiceForPsc.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/TimeManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/TimeSharedMemory.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZone.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarAdditionalInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/CalendarTime.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeTypeInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TimeZoneRule.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/TimeZone/Types/TzifHeader.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Types/SteadyClockContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Time/Types/TimePermissions.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Usb/IClientRootSession.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Usb/IDsService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Usb/IPdCradleManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Usb/IPdManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Usb/IPmService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Usb/IUnknown1.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Usb/IUnknown2.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/IApplicationRootService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/IManagerRootService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/ISystemRootService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/ResultCode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/AndroidSurfaceComposerClient.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/IManagerDisplayService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/ISystemDisplayService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/DestinationScalingMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/DisplayInfo.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/Types/SourceScalingMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Vi/Types/ViServiceType.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Wlan/IInfraManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Wlan/ILocalGetActionFrame.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Wlan/ILocalGetFrame.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Wlan/ILocalManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Wlan/ISocketGetFrame.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Wlan/ISocketManager.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Wlan/IUnknown1.cs create mode 100644 src/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs create mode 100644 src/Ryujinx.HLE/HOS/SystemState/ColorSet.cs create mode 100644 src/Ryujinx.HLE/HOS/SystemState/KeyboardLayout.cs create mode 100644 src/Ryujinx.HLE/HOS/SystemState/RegionCode.cs create mode 100644 src/Ryujinx.HLE/HOS/SystemState/SystemLanguage.cs create mode 100644 src/Ryujinx.HLE/HOS/SystemState/SystemStateMgr.cs create mode 100644 src/Ryujinx.HLE/HOS/SystemState/TitleLanguage.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/AtmosphereCompiler.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/AtmosphereProgram.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/Arithmetic.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/BeginConditionalBlock.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/DebugLog.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/EndConditionalBlock.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/KeyPressConditional.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LegacyArithmetic.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LoadRegisterWithConstant.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/LoadRegisterWithMemory.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/MemoryConditional.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/PauseProcess.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/ReadOrWriteStaticRegister.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/RegisterConditional.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/ResumeProcess.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/SaveOrRestoreRegister.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/SaveOrRestoreRegisterWithMask.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StartEndLoop.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToAddress.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreConstantToMemory.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeEmitters/StoreRegisterToMemory.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CodeType.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Comparison.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/CompilationContext.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Conditions/CondEQ.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Conditions/CondGE.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Conditions/CondGT.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Conditions/CondLE.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Conditions/CondLT.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Conditions/CondNE.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Conditions/ICondition.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Conditions/InputMask.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/ITamperProgram.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/ITamperedProcess.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/InstructionHelper.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/MemoryHelper.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/MemoryRegion.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/OperationBlock.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/Block.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/ForBlock.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/IOperand.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/IOperation.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/IfBlock.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpAdd.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpAnd.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpLog.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpLsh.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpMov.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpMul.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpNot.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpOr.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpProcCtrl.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpRsh.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpSub.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Operations/OpXor.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Parameter.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Pointer.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Register.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/TamperedKProcess.cs create mode 100644 src/Ryujinx.HLE/HOS/Tamper/Value.cs create mode 100644 src/Ryujinx.HLE/HOS/TamperMachine.cs create mode 100644 src/Ryujinx.HLE/HOS/UserChannelPersistence.cs create mode 100644 src/Ryujinx.HLE/Homebrew.npdm create mode 100644 src/Ryujinx.HLE/Loaders/Elf/ElfDynamic.cs create mode 100644 src/Ryujinx.HLE/Loaders/Elf/ElfDynamicTag.cs create mode 100644 src/Ryujinx.HLE/Loaders/Elf/ElfSymbol.cs create mode 100644 src/Ryujinx.HLE/Loaders/Elf/ElfSymbol32.cs create mode 100644 src/Ryujinx.HLE/Loaders/Elf/ElfSymbol64.cs create mode 100644 src/Ryujinx.HLE/Loaders/Elf/ElfSymbolBinding.cs create mode 100644 src/Ryujinx.HLE/Loaders/Elf/ElfSymbolType.cs create mode 100644 src/Ryujinx.HLE/Loaders/Elf/ElfSymbolVisibility.cs create mode 100644 src/Ryujinx.HLE/Loaders/Executables/IExecutable.cs create mode 100644 src/Ryujinx.HLE/Loaders/Executables/KipExecutable.cs create mode 100644 src/Ryujinx.HLE/Loaders/Executables/NroExecutable.cs create mode 100644 src/Ryujinx.HLE/Loaders/Executables/NsoExecutable.cs create mode 100644 src/Ryujinx.HLE/Loaders/Mods/IPSPatcher.cs create mode 100644 src/Ryujinx.HLE/Loaders/Mods/IPSwitchPatcher.cs create mode 100644 src/Ryujinx.HLE/Loaders/Mods/MemPatch.cs create mode 100644 src/Ryujinx.HLE/Loaders/Npdm/ACI0.cs create mode 100644 src/Ryujinx.HLE/Loaders/Npdm/ACID.cs create mode 100644 src/Ryujinx.HLE/Loaders/Npdm/FsAccessControl.cs create mode 100644 src/Ryujinx.HLE/Loaders/Npdm/FsAccessHeader.cs create mode 100644 src/Ryujinx.HLE/Loaders/Npdm/KernelAccessControl.cs create mode 100644 src/Ryujinx.HLE/Loaders/Npdm/Npdm.cs create mode 100644 src/Ryujinx.HLE/Loaders/Npdm/ServiceAccessControl.cs create mode 100644 src/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs create mode 100644 src/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs create mode 100644 src/Ryujinx.HLE/Loaders/Processes/Extensions/MetaLoaderExtensions.cs create mode 100644 src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs create mode 100644 src/Ryujinx.HLE/Loaders/Processes/Extensions/PartitionFileSystemExtensions.cs create mode 100644 src/Ryujinx.HLE/Loaders/Processes/ProcessConst.cs create mode 100644 src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs create mode 100644 src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs create mode 100644 src/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs create mode 100644 src/Ryujinx.HLE/MemoryConfiguration.cs create mode 100644 src/Ryujinx.HLE/PerformanceStatistics.cs create mode 100644 src/Ryujinx.HLE/Ryujinx.HLE.csproj create mode 100644 src/Ryujinx.HLE/Switch.cs create mode 100644 src/Ryujinx.HLE/Ui/DynamicTextChangedHandler.cs create mode 100644 src/Ryujinx.HLE/Ui/IDynamicTextInputHandler.cs create mode 100644 src/Ryujinx.HLE/Ui/IHostUiHandler.cs create mode 100644 src/Ryujinx.HLE/Ui/IHostUiTheme.cs create mode 100644 src/Ryujinx.HLE/Ui/Input/NpadButtonHandler.cs create mode 100644 src/Ryujinx.HLE/Ui/Input/NpadReader.cs create mode 100644 src/Ryujinx.HLE/Ui/KeyPressedHandler.cs create mode 100644 src/Ryujinx.HLE/Ui/KeyReleasedHandler.cs create mode 100644 src/Ryujinx.HLE/Ui/RenderingSurfaceInfo.cs create mode 100644 src/Ryujinx.HLE/Ui/ThemeColor.cs create mode 100644 src/Ryujinx.HLE/Utilities/StringUtils.cs create mode 100644 src/Ryujinx.Headless.SDL2/HeadlessDynamicTextInputHandler.cs create mode 100644 src/Ryujinx.Headless.SDL2/HeadlessHostUiTheme.cs create mode 100644 src/Ryujinx.Headless.SDL2/HideCursor.cs create mode 100644 src/Ryujinx.Headless.SDL2/OpenGL/OpenGLWindow.cs create mode 100644 src/Ryujinx.Headless.SDL2/Options.cs create mode 100644 src/Ryujinx.Headless.SDL2/Program.cs create mode 100644 src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj create mode 100644 src/Ryujinx.Headless.SDL2/Ryujinx.bmp create mode 100644 src/Ryujinx.Headless.SDL2/SDL2Mouse.cs create mode 100644 src/Ryujinx.Headless.SDL2/SDL2MouseDriver.cs create mode 100644 src/Ryujinx.Headless.SDL2/StatusUpdatedEventArgs.cs create mode 100644 src/Ryujinx.Headless.SDL2/Vulkan/VulkanWindow.cs create mode 100644 src/Ryujinx.Headless.SDL2/WindowBase.cs create mode 100644 src/Ryujinx.Horizon.Common/ISyscallApi.cs create mode 100644 src/Ryujinx.Horizon.Common/IThreadContext.cs create mode 100644 src/Ryujinx.Horizon.Common/InvalidResultException.cs create mode 100644 src/Ryujinx.Horizon.Common/KernelResult.cs create mode 100644 src/Ryujinx.Horizon.Common/OnScopeExit.cs create mode 100644 src/Ryujinx.Horizon.Common/Result.cs create mode 100644 src/Ryujinx.Horizon.Common/ResultNames.cs create mode 100644 src/Ryujinx.Horizon.Common/Ryujinx.Horizon.Common.csproj create mode 100644 src/Ryujinx.Horizon.Common/ThreadTerminatedException.cs create mode 100644 src/Ryujinx.Horizon.Generators/CodeGenerator.cs create mode 100644 src/Ryujinx.Horizon.Generators/Hipc/CommandArgType.cs create mode 100644 src/Ryujinx.Horizon.Generators/Hipc/CommandInterface.cs create mode 100644 src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs create mode 100644 src/Ryujinx.Horizon.Generators/Hipc/HipcSyntaxReceiver.cs create mode 100644 src/Ryujinx.Horizon.Generators/Ryujinx.Horizon.Generators.csproj create mode 100644 src/Ryujinx.Horizon.Kernel.Generators/CodeGenerator.cs create mode 100644 src/Ryujinx.Horizon.Kernel.Generators/Kernel/SyscallGenerator.cs create mode 100644 src/Ryujinx.Horizon.Kernel.Generators/Kernel/SyscallSyntaxReceiver.cs create mode 100644 src/Ryujinx.Horizon.Kernel.Generators/Ryujinx.Horizon.Kernel.Generators.csproj create mode 100644 src/Ryujinx.Horizon/HeapAllocator.cs create mode 100644 src/Ryujinx.Horizon/HorizonOptions.cs create mode 100644 src/Ryujinx.Horizon/HorizonStatic.cs create mode 100644 src/Ryujinx.Horizon/IService.cs create mode 100644 src/Ryujinx.Horizon/LogManager/Ipc/LmLogger.cs create mode 100644 src/Ryujinx.Horizon/LogManager/Ipc/LogService.cs create mode 100644 src/Ryujinx.Horizon/LogManager/LmIpcServer.cs create mode 100644 src/Ryujinx.Horizon/LogManager/LmMain.cs create mode 100644 src/Ryujinx.Horizon/LogManager/Types/LogPacket.cs create mode 100644 src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs create mode 100644 src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs create mode 100644 src/Ryujinx.Horizon/Prepo/PrepoMain.cs create mode 100644 src/Ryujinx.Horizon/Prepo/PrepoResult.cs create mode 100644 src/Ryujinx.Horizon/Prepo/PrepoServerManager.cs create mode 100644 src/Ryujinx.Horizon/Prepo/Types/PrepoPortIndex.cs create mode 100644 src/Ryujinx.Horizon/Prepo/Types/PrepoServicePermissionLevel.cs create mode 100644 src/Ryujinx.Horizon/Ryujinx.Horizon.csproj create mode 100644 src/Ryujinx.Horizon/Sdk/Account/Uid.cs create mode 100644 src/Ryujinx.Horizon/Sdk/DebugUtil.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Diag/LogSeverity.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Lm/ILmLogger.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Lm/ILogService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Lm/LogDataChunkKey.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Lm/LogDestination.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Lm/LogPacketFlags.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Lm/LogPacketHeader.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Ncm/ApplicationId.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/Event.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/EventType.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEvent.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/Impl/InterProcessEventImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/InitializationState.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/InterProcessEventType.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderBase.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfEvent.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfHandle.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/OsMultiWait.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/OsProcessHandle.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/OsResult.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/OsSystemEvent.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/OsThreadManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Prepo/IPrepoService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/ServiceUtil.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainInHeader.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainOutHeader.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifDomainRequestType.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifInHeader.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifMessage.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifOutHeader.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequestFormat.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifResponse.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/CommandType.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObject.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectDispatchTable.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/DomainServiceObjectProcessor.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/HandlesToClose.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/InlineContext.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/PointerAndSize.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/ScopedInlineContextChange.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainBase.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerDomainManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageProcessor.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServerMessageRuntimeMetadata.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchContext.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchMeta.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTable.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceDispatchTableBase.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Cmif/ServiceObjectHolder.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/CmifCommandAttribute.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/CommandArg.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/CommandArgAttributes.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/CommandHandler.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/Api.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/Header.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferFlags.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferMode.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessage.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMessageData.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcMetadata.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcReceiveListEntry.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcResult.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcStaticDescriptor.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/ManagerOptions.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/ReceiveResult.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/Server.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerDomainSessionManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerManagerBase.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSession.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/ServerSessionManager.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/Hipc/SpecialHeader.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/HipcCommandProcessor.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/IServiceObject.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/RawDataOffsetCalculator.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sf/SfResult.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sm/IManagerService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sm/IUserService.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sm/ServiceName.cs create mode 100644 src/Ryujinx.Horizon/Sdk/Sm/SmApi.cs create mode 100644 src/Ryujinx.Horizon/ServiceEntry.cs create mode 100644 src/Ryujinx.Horizon/ServiceTable.cs create mode 100644 src/Ryujinx.Horizon/Sm/Impl/ServiceInfo.cs create mode 100644 src/Ryujinx.Horizon/Sm/Impl/ServiceManager.cs create mode 100644 src/Ryujinx.Horizon/Sm/Ipc/ManagerService.cs create mode 100644 src/Ryujinx.Horizon/Sm/Ipc/UserService.cs create mode 100644 src/Ryujinx.Horizon/Sm/SmMain.cs create mode 100644 src/Ryujinx.Horizon/Sm/SmResult.cs create mode 100644 src/Ryujinx.Horizon/Sm/SmServerManager.cs create mode 100644 src/Ryujinx.Horizon/Sm/Types/SmPortIndex.cs create mode 100644 src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj create mode 100644 src/Ryujinx.Input.SDL2/SDL2Gamepad.cs create mode 100644 src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs create mode 100644 src/Ryujinx.Input.SDL2/SDL2Keyboard.cs create mode 100644 src/Ryujinx.Input.SDL2/SDLKeyboardDriver.cs create mode 100644 src/Ryujinx.Input/Assigner/GamepadButtonAssigner.cs create mode 100644 src/Ryujinx.Input/Assigner/IButtonAssigner.cs create mode 100644 src/Ryujinx.Input/Assigner/KeyboardKeyAssigner.cs create mode 100644 src/Ryujinx.Input/GamepadButtonInputId.cs create mode 100644 src/Ryujinx.Input/GamepadFeaturesFlag.cs create mode 100644 src/Ryujinx.Input/GamepadStateSnapshot.cs create mode 100644 src/Ryujinx.Input/HLE/InputManager.cs create mode 100644 src/Ryujinx.Input/HLE/NpadController.cs create mode 100644 src/Ryujinx.Input/HLE/NpadManager.cs create mode 100644 src/Ryujinx.Input/HLE/TouchScreenManager.cs create mode 100644 src/Ryujinx.Input/IGamepad.cs create mode 100644 src/Ryujinx.Input/IGamepadDriver.cs create mode 100644 src/Ryujinx.Input/IKeyboard.cs create mode 100644 src/Ryujinx.Input/IMouse.cs create mode 100644 src/Ryujinx.Input/Key.cs create mode 100644 src/Ryujinx.Input/KeyboardStateSnapshot.cs create mode 100644 src/Ryujinx.Input/Motion/CemuHook/Client.cs create mode 100644 src/Ryujinx.Input/Motion/CemuHook/Protocol/ControllerData.cs create mode 100644 src/Ryujinx.Input/Motion/CemuHook/Protocol/ControllerInfo.cs create mode 100644 src/Ryujinx.Input/Motion/CemuHook/Protocol/Header.cs create mode 100644 src/Ryujinx.Input/Motion/CemuHook/Protocol/MessageType.cs create mode 100644 src/Ryujinx.Input/Motion/CemuHook/Protocol/SharedResponse.cs create mode 100644 src/Ryujinx.Input/Motion/MotionInput.cs create mode 100644 src/Ryujinx.Input/Motion/MotionSensorFilter.cs create mode 100644 src/Ryujinx.Input/MotionInputId.cs create mode 100644 src/Ryujinx.Input/MouseButton.cs create mode 100644 src/Ryujinx.Input/MouseStateSnapshot.cs create mode 100644 src/Ryujinx.Input/Ryujinx.Input.csproj create mode 100644 src/Ryujinx.Input/StickInputId.cs create mode 100644 src/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs create mode 100644 src/Ryujinx.Memory.Tests/MultiRegionTrackingTests.cs create mode 100644 src/Ryujinx.Memory.Tests/Ryujinx.Memory.Tests.csproj create mode 100644 src/Ryujinx.Memory.Tests/Tests.cs create mode 100644 src/Ryujinx.Memory.Tests/TrackingTests.cs create mode 100644 src/Ryujinx.Memory/AddressSpaceManager.cs create mode 100644 src/Ryujinx.Memory/IRefCounted.cs create mode 100644 src/Ryujinx.Memory/IVirtualMemoryManager.cs create mode 100644 src/Ryujinx.Memory/IWritableBlock.cs create mode 100644 src/Ryujinx.Memory/InvalidAccessHandler.cs create mode 100644 src/Ryujinx.Memory/InvalidMemoryRegionException.cs create mode 100644 src/Ryujinx.Memory/MemoryAllocationFlags.cs create mode 100644 src/Ryujinx.Memory/MemoryBlock.cs create mode 100644 src/Ryujinx.Memory/MemoryConstants.cs create mode 100644 src/Ryujinx.Memory/MemoryManagement.cs create mode 100644 src/Ryujinx.Memory/MemoryManagementUnix.cs create mode 100644 src/Ryujinx.Memory/MemoryManagementWindows.cs create mode 100644 src/Ryujinx.Memory/MemoryManagerUnixHelper.cs create mode 100644 src/Ryujinx.Memory/MemoryMapFlags.cs create mode 100644 src/Ryujinx.Memory/MemoryNotContiguousException.cs create mode 100644 src/Ryujinx.Memory/MemoryPermission.cs create mode 100644 src/Ryujinx.Memory/MemoryProtectionException.cs create mode 100644 src/Ryujinx.Memory/NativeMemoryManager.cs create mode 100644 src/Ryujinx.Memory/PageTable.cs create mode 100644 src/Ryujinx.Memory/Range/HostMemoryRange.cs create mode 100644 src/Ryujinx.Memory/Range/IMultiRangeItem.cs create mode 100644 src/Ryujinx.Memory/Range/INonOverlappingRange.cs create mode 100644 src/Ryujinx.Memory/Range/IRange.cs create mode 100644 src/Ryujinx.Memory/Range/MemoryRange.cs create mode 100644 src/Ryujinx.Memory/Range/MultiRange.cs create mode 100644 src/Ryujinx.Memory/Range/MultiRangeList.cs create mode 100644 src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs create mode 100644 src/Ryujinx.Memory/Range/RangeList.cs create mode 100644 src/Ryujinx.Memory/Ryujinx.Memory.csproj create mode 100644 src/Ryujinx.Memory/Tracking/AbstractRegion.cs create mode 100644 src/Ryujinx.Memory/Tracking/BitMap.cs create mode 100644 src/Ryujinx.Memory/Tracking/ConcurrentBitmap.cs create mode 100644 src/Ryujinx.Memory/Tracking/IMultiRegionHandle.cs create mode 100644 src/Ryujinx.Memory/Tracking/IRegionHandle.cs create mode 100644 src/Ryujinx.Memory/Tracking/MemoryTracking.cs create mode 100644 src/Ryujinx.Memory/Tracking/MultiRegionHandle.cs create mode 100644 src/Ryujinx.Memory/Tracking/PreciseRegionSignal.cs create mode 100644 src/Ryujinx.Memory/Tracking/RegionHandle.cs create mode 100644 src/Ryujinx.Memory/Tracking/RegionSignal.cs create mode 100644 src/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs create mode 100644 src/Ryujinx.Memory/Tracking/VirtualRegion.cs create mode 100644 src/Ryujinx.Memory/WindowsShared/MappingTree.cs create mode 100644 src/Ryujinx.Memory/WindowsShared/PlaceholderManager.cs create mode 100644 src/Ryujinx.Memory/WindowsShared/WindowsApi.cs create mode 100644 src/Ryujinx.Memory/WindowsShared/WindowsApiException.cs create mode 100644 src/Ryujinx.Memory/WindowsShared/WindowsFlags.cs create mode 100644 src/Ryujinx.Memory/WritableRegion.cs create mode 100644 src/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj create mode 100644 src/Ryujinx.SDL2.Common/SDL2Driver.cs create mode 100644 src/Ryujinx.ShaderTools/Program.cs create mode 100644 src/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj create mode 100644 src/Ryujinx.Tests.Unicorn/IndexedProperty.cs create mode 100644 src/Ryujinx.Tests.Unicorn/MemoryPermission.cs create mode 100644 src/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj create mode 100644 src/Ryujinx.Tests.Unicorn/SimdValue.cs create mode 100644 src/Ryujinx.Tests.Unicorn/UnicornAArch32.cs create mode 100644 src/Ryujinx.Tests.Unicorn/UnicornAArch64.cs create mode 100644 src/Ryujinx.Tests/.runsettings create mode 100644 src/Ryujinx.Tests/Audio/Renderer/AudioRendererConfigurationTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/BehaviourParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/BiquadFilterParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Common/UpdateDataHeaderTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Common/VoiceUpdateStateTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Common/WaveBufferTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Dsp/ResamplerTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Dsp/UpsamplerTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/EffectInfoParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/EffectOutStatusTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/MemoryPoolParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/BehaviourErrorInfoOutStatusTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/AuxParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/BufferMixerParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/CompressorParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/DelayParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/LimiterStatisticsTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/Reverb3dParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/Effect/ReverbParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdateTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/MixParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/PerformanceInParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/PerformanceOutStatusTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/RendererInfoOutStatusTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/Sink/CircularBufferParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/Sink/DeviceParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/SinkInParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/SinkOutStatusTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Parameter/SplitterInParamHeaderTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Server/AddressInfoTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Server/BehaviourContextTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Server/MemoryPoolStateTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Server/MixStateTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Server/PoolMapperTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Server/SplitterDestinationTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Server/SplitterStateTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Server/VoiceChannelResourceTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Server/VoiceStateTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/Server/WaveBufferTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/VoiceChannelResourceInParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/VoiceInParameterTests.cs create mode 100644 src/Ryujinx.Tests/Audio/Renderer/VoiceOutStatusTests.cs create mode 100644 src/Ryujinx.Tests/Cpu/Arm64CodeGenCommonTests.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuContext.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTest.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTest32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestAlu.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestAlu32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestAluBinary.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestAluBinary32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestAluImm.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestAluImm32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestAluRs.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestAluRs32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestAluRx.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestBf32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestBfm.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestCcmpImm.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestCcmpReg.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestCsel.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestMisc.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestMisc32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestMov.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestMul.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestMul32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimd.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimd32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdCrypto.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdCrypto32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdCvt.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdCvt32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdExt.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdFcond.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdFmov.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdImm.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdIns.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdLogical32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdMemory32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdMov32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdReg.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdReg32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdRegElem.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdRegElem32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdRegElemF.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdShImm.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdShImm32.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSimdTbl.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestSystem.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestT32Alu.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestT32Flow.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestT32Mem.cs create mode 100644 src/Ryujinx.Tests/Cpu/CpuTestThumb.cs create mode 100644 src/Ryujinx.Tests/Cpu/EnvironmentTests.cs create mode 100644 src/Ryujinx.Tests/Cpu/PrecomputedMemoryThumbTestCase.cs create mode 100644 src/Ryujinx.Tests/Cpu/PrecomputedThumbTestCase.cs create mode 100644 src/Ryujinx.Tests/HLE/SoftwareKeyboardTests.cs create mode 100644 src/Ryujinx.Tests/Memory/MockMemoryManager.cs create mode 100644 src/Ryujinx.Tests/Memory/PartialUnmaps.cs create mode 100644 src/Ryujinx.Tests/Ryujinx.Tests.csproj create mode 100644 src/Ryujinx.Tests/Time/TimeZoneRuleTests.cs create mode 100644 src/Ryujinx.Tests/TreeDictionaryTests.cs create mode 100644 src/Ryujinx.Ui.Common/App/ApplicationAddedEventArgs.cs create mode 100644 src/Ryujinx.Ui.Common/App/ApplicationCountUpdatedEventArgs.cs create mode 100644 src/Ryujinx.Ui.Common/App/ApplicationData.cs create mode 100644 src/Ryujinx.Ui.Common/App/ApplicationJsonSerializerContext.cs create mode 100644 src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs create mode 100644 src/Ryujinx.Ui.Common/App/ApplicationMetadata.cs create mode 100644 src/Ryujinx.Ui.Common/Configuration/AudioBackend.cs create mode 100644 src/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormat.cs create mode 100644 src/Ryujinx.Ui.Common/Configuration/ConfigurationFileFormatSettings.cs create mode 100644 src/Ryujinx.Ui.Common/Configuration/ConfigurationJsonSerializerContext.cs create mode 100644 src/Ryujinx.Ui.Common/Configuration/ConfigurationLoadResult.cs create mode 100644 src/Ryujinx.Ui.Common/Configuration/ConfigurationState.cs create mode 100644 src/Ryujinx.Ui.Common/Configuration/FileTypes.cs create mode 100644 src/Ryujinx.Ui.Common/Configuration/LoggerModule.cs create mode 100644 src/Ryujinx.Ui.Common/Configuration/System/Language.cs create mode 100644 src/Ryujinx.Ui.Common/Configuration/System/Region.cs create mode 100644 src/Ryujinx.Ui.Common/Configuration/Ui/ColumnSort.cs create mode 100644 src/Ryujinx.Ui.Common/Configuration/Ui/GuiColumns.cs create mode 100644 src/Ryujinx.Ui.Common/Configuration/Ui/ShownFileTypes.cs create mode 100644 src/Ryujinx.Ui.Common/DiscordIntegrationModule.cs create mode 100644 src/Ryujinx.Ui.Common/Extensions/FileTypeExtensions.cs create mode 100644 src/Ryujinx.Ui.Common/Helper/CommandLineState.cs create mode 100644 src/Ryujinx.Ui.Common/Helper/ConsoleHelper.cs create mode 100644 src/Ryujinx.Ui.Common/Helper/FileAssociationHelper.cs create mode 100644 src/Ryujinx.Ui.Common/Helper/ObjectiveC.cs create mode 100644 src/Ryujinx.Ui.Common/Helper/OpenHelper.cs create mode 100644 src/Ryujinx.Ui.Common/Helper/SetupValidator.cs create mode 100644 src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApi.cs create mode 100644 src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApiGamesSwitch.cs create mode 100644 src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboApiUsage.cs create mode 100644 src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboJson.cs create mode 100644 src/Ryujinx.Ui.Common/Models/Amiibo/AmiiboJsonSerializerContext.cs create mode 100644 src/Ryujinx.Ui.Common/Models/Github/GithubReleaseAssetJsonResponse.cs create mode 100644 src/Ryujinx.Ui.Common/Models/Github/GithubReleasesJsonResponse.cs create mode 100644 src/Ryujinx.Ui.Common/Models/Github/GithubReleasesJsonSerializerContext.cs create mode 100644 src/Ryujinx.Ui.Common/Resources/Controller_JoyConLeft.svg create mode 100644 src/Ryujinx.Ui.Common/Resources/Controller_JoyConPair.svg create mode 100644 src/Ryujinx.Ui.Common/Resources/Controller_JoyConRight.svg create mode 100644 src/Ryujinx.Ui.Common/Resources/Controller_ProCon.svg create mode 100644 src/Ryujinx.Ui.Common/Resources/Icon_NCA.png create mode 100644 src/Ryujinx.Ui.Common/Resources/Icon_NRO.png create mode 100644 src/Ryujinx.Ui.Common/Resources/Icon_NSO.png create mode 100644 src/Ryujinx.Ui.Common/Resources/Icon_NSP.png create mode 100644 src/Ryujinx.Ui.Common/Resources/Icon_XCI.png create mode 100644 src/Ryujinx.Ui.Common/Resources/Logo_Amiibo.png create mode 100644 src/Ryujinx.Ui.Common/Resources/Logo_Discord_Dark.png create mode 100644 src/Ryujinx.Ui.Common/Resources/Logo_Discord_Light.png create mode 100644 src/Ryujinx.Ui.Common/Resources/Logo_GitHub_Dark.png create mode 100644 src/Ryujinx.Ui.Common/Resources/Logo_GitHub_Light.png create mode 100644 src/Ryujinx.Ui.Common/Resources/Logo_Patreon_Dark.png create mode 100644 src/Ryujinx.Ui.Common/Resources/Logo_Patreon_Light.png create mode 100644 src/Ryujinx.Ui.Common/Resources/Logo_Ryujinx.png create mode 100644 src/Ryujinx.Ui.Common/Resources/Logo_Twitter_Dark.png create mode 100644 src/Ryujinx.Ui.Common/Resources/Logo_Twitter_Light.png create mode 100644 src/Ryujinx.Ui.Common/Ryujinx.Ui.Common.csproj create mode 100644 src/Ryujinx.Ui.Common/UserError.cs create mode 100644 src/Ryujinx.Ui.LocaleGenerator/LocaleGenerator.cs create mode 100644 src/Ryujinx.Ui.LocaleGenerator/Ryujinx.Ui.LocaleGenerator.csproj create mode 100644 src/Ryujinx/Input/GTK3/GTK3Keyboard.cs create mode 100644 src/Ryujinx/Input/GTK3/GTK3KeyboardDriver.cs create mode 100644 src/Ryujinx/Input/GTK3/GTK3MappingHelper.cs create mode 100644 src/Ryujinx/Input/GTK3/GTK3Mouse.cs create mode 100644 src/Ryujinx/Input/GTK3/GTK3MouseDriver.cs create mode 100644 src/Ryujinx/Modules/Updater/UpdateDialog.cs create mode 100644 src/Ryujinx/Modules/Updater/UpdateDialog.glade create mode 100644 src/Ryujinx/Modules/Updater/Updater.cs create mode 100644 src/Ryujinx/Program.cs create mode 100644 src/Ryujinx/Ryujinx.csproj create mode 100644 src/Ryujinx/Ryujinx.ico create mode 100644 src/Ryujinx/Ui/Applet/ErrorAppletDialog.cs create mode 100644 src/Ryujinx/Ui/Applet/GtkDynamicTextInputHandler.cs create mode 100644 src/Ryujinx/Ui/Applet/GtkHostUiHandler.cs create mode 100644 src/Ryujinx/Ui/Applet/GtkHostUiTheme.cs create mode 100644 src/Ryujinx/Ui/Applet/SwkbdAppletDialog.cs create mode 100644 src/Ryujinx/Ui/GLRenderer.cs create mode 100644 src/Ryujinx/Ui/Helper/MetalHelper.cs create mode 100644 src/Ryujinx/Ui/Helper/SortHelper.cs create mode 100644 src/Ryujinx/Ui/Helper/ThemeHelper.cs create mode 100644 src/Ryujinx/Ui/MainWindow.cs create mode 100644 src/Ryujinx/Ui/MainWindow.glade create mode 100644 src/Ryujinx/Ui/OpenToolkitBindingsContext.cs create mode 100644 src/Ryujinx/Ui/RendererWidgetBase.cs create mode 100644 src/Ryujinx/Ui/SPBOpenGLContext.cs create mode 100644 src/Ryujinx/Ui/StatusUpdatedEventArgs.cs create mode 100644 src/Ryujinx/Ui/VKRenderer.cs create mode 100644 src/Ryujinx/Ui/Widgets/GameTableContextMenu.Designer.cs create mode 100644 src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs create mode 100644 src/Ryujinx/Ui/Widgets/GtkDialog.cs create mode 100644 src/Ryujinx/Ui/Widgets/GtkInputDialog.cs create mode 100644 src/Ryujinx/Ui/Widgets/ProfileDialog.cs create mode 100644 src/Ryujinx/Ui/Widgets/ProfileDialog.glade create mode 100644 src/Ryujinx/Ui/Widgets/RawInputToTextEntry.cs create mode 100644 src/Ryujinx/Ui/Widgets/UserErrorDialog.cs create mode 100644 src/Ryujinx/Ui/Windows/AboutWindow.Designer.cs create mode 100644 src/Ryujinx/Ui/Windows/AboutWindow.cs create mode 100644 src/Ryujinx/Ui/Windows/AmiiboWindow.Designer.cs create mode 100644 src/Ryujinx/Ui/Windows/AmiiboWindow.cs create mode 100644 src/Ryujinx/Ui/Windows/AvatarWindow.cs create mode 100644 src/Ryujinx/Ui/Windows/CheatWindow.cs create mode 100644 src/Ryujinx/Ui/Windows/CheatWindow.glade create mode 100644 src/Ryujinx/Ui/Windows/ControllerWindow.cs create mode 100644 src/Ryujinx/Ui/Windows/ControllerWindow.glade create mode 100644 src/Ryujinx/Ui/Windows/DlcWindow.cs create mode 100644 src/Ryujinx/Ui/Windows/DlcWindow.glade create mode 100644 src/Ryujinx/Ui/Windows/SettingsWindow.cs create mode 100644 src/Ryujinx/Ui/Windows/SettingsWindow.glade create mode 100644 src/Ryujinx/Ui/Windows/TitleUpdateWindow.cs create mode 100644 src/Ryujinx/Ui/Windows/TitleUpdateWindow.glade create mode 100644 src/Ryujinx/Ui/Windows/UserProfilesManagerWindow.Designer.cs create mode 100644 src/Ryujinx/Ui/Windows/UserProfilesManagerWindow.cs create mode 100644 src/Spv.Generator/Autogenerated/CoreGrammar.cs create mode 100644 src/Spv.Generator/Autogenerated/GlslStd450Grammar.cs create mode 100644 src/Spv.Generator/Autogenerated/OpenClGrammar.cs create mode 100644 src/Spv.Generator/ConstantKey.cs create mode 100644 src/Spv.Generator/DeterministicHashCode.cs create mode 100644 src/Spv.Generator/DeterministicStringKey.cs create mode 100644 src/Spv.Generator/GeneratorPool.cs create mode 100644 src/Spv.Generator/Instruction.cs create mode 100644 src/Spv.Generator/InstructionOperands.cs create mode 100644 src/Spv.Generator/LICENSE create mode 100644 src/Spv.Generator/LiteralInteger.cs create mode 100644 src/Spv.Generator/LiteralString.cs create mode 100644 src/Spv.Generator/Module.cs create mode 100644 src/Spv.Generator/Operand.cs create mode 100644 src/Spv.Generator/OperandType.cs create mode 100644 src/Spv.Generator/Spv.Generator.csproj create mode 100644 src/Spv.Generator/TypeDeclarationKey.cs create mode 100644 src/Spv.Generator/spirv.cs (limited to 'src') diff --git a/src/ARMeilleure/ARMeilleure.csproj b/src/ARMeilleure/ARMeilleure.csproj new file mode 100644 index 00000000..fa555115 --- /dev/null +++ b/src/ARMeilleure/ARMeilleure.csproj @@ -0,0 +1,26 @@ + + + + net7.0 + true + + + + + + + + + + PreserveNewest + libarmeilleure-jitsupport.dylib + + + + + + <_Parameter1>Ryujinx.Tests + + + + diff --git a/src/ARMeilleure/Allocators.cs b/src/ARMeilleure/Allocators.cs new file mode 100644 index 00000000..deabf9a2 --- /dev/null +++ b/src/ARMeilleure/Allocators.cs @@ -0,0 +1,42 @@ +using ARMeilleure.Common; +using System; +using System.Runtime.CompilerServices; + +namespace ARMeilleure +{ + static class Allocators + { + [ThreadStatic] private static ArenaAllocator _default; + [ThreadStatic] private static ArenaAllocator _operands; + [ThreadStatic] private static ArenaAllocator _operations; + [ThreadStatic] private static ArenaAllocator _references; + [ThreadStatic] private static ArenaAllocator _liveRanges; + [ThreadStatic] private static ArenaAllocator _liveIntervals; + + public static ArenaAllocator Default => GetAllocator(ref _default, 256 * 1024, 4); + public static ArenaAllocator Operands => GetAllocator(ref _operands, 64 * 1024, 8); + public static ArenaAllocator Operations => GetAllocator(ref _operations, 64 * 1024, 8); + public static ArenaAllocator References => GetAllocator(ref _references, 64 * 1024, 8); + public static ArenaAllocator LiveRanges => GetAllocator(ref _liveRanges, 64 * 1024, 8); + public static ArenaAllocator LiveIntervals => GetAllocator(ref _liveIntervals, 64 * 1024, 8); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ArenaAllocator GetAllocator(ref ArenaAllocator alloc, uint pageSize, uint pageCount) + { + if (alloc == null) + { + alloc = new ArenaAllocator(pageSize, pageCount); + } + + return alloc; + } + + public static void ResetAll() + { + Default.Reset(); + Operands.Reset(); + Operations.Reset(); + References.Reset(); + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/Arm64Optimizer.cs b/src/ARMeilleure/CodeGen/Arm64/Arm64Optimizer.cs new file mode 100644 index 00000000..fdd4d024 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/Arm64Optimizer.cs @@ -0,0 +1,270 @@ +using ARMeilleure.CodeGen.Optimizations; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System.Collections.Generic; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static class Arm64Optimizer + { + private const int MaxConstantUses = 10000; + + public static void RunPass(ControlFlowGraph cfg) + { + var constants = new Dictionary(); + + Operand GetConstantCopy(BasicBlock block, Operation operation, Operand source) + { + // If the constant has many uses, we also force a new constant mov to be added, in order + // to avoid overflow of the counts field (that is limited to 16 bits). + if (!constants.TryGetValue(source.Value, out var constant) || constant.UsesCount > MaxConstantUses) + { + constant = Local(source.Type); + + Operation copyOp = Operation(Instruction.Copy, constant, source); + + block.Operations.AddBefore(operation, copyOp); + + constants[source.Value] = constant; + } + + return constant; + } + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + constants.Clear(); + + Operation nextNode; + + for (Operation node = block.Operations.First; node != default; node = nextNode) + { + nextNode = node.ListNext; + + // Insert copies for constants that can't fit on a 32-bit immediate. + // Doing this early unblocks a few optimizations. + if (node.Instruction == Instruction.Add) + { + Operand src1 = node.GetSource(0); + Operand src2 = node.GetSource(1); + + if (src1.Kind == OperandKind.Constant && (src1.Relocatable || ConstTooLong(src1, OperandType.I32))) + { + node.SetSource(0, GetConstantCopy(block, node, src1)); + } + + if (src2.Kind == OperandKind.Constant && (src2.Relocatable || ConstTooLong(src2, OperandType.I32))) + { + node.SetSource(1, GetConstantCopy(block, node, src2)); + } + } + + // Try to fold something like: + // lsl x1, x1, #2 + // add x0, x0, x1 + // ldr x0, [x0] + // add x2, x2, #16 + // ldr x2, [x2] + // Into: + // ldr x0, [x0, x1, lsl #2] + // ldr x2, [x2, #16] + if (IsMemoryLoadOrStore(node.Instruction)) + { + OperandType type; + + if (node.Destination != default) + { + type = node.Destination.Type; + } + else + { + type = node.GetSource(1).Type; + } + + Operand memOp = GetMemoryOperandOrNull(node.GetSource(0), type); + + if (memOp != default) + { + node.SetSource(0, memOp); + } + } + } + } + + Optimizer.RemoveUnusedNodes(cfg); + } + + private static Operand GetMemoryOperandOrNull(Operand addr, OperandType type) + { + Operand baseOp = addr; + + // First we check if the address is the result of a local X with immediate + // addition. If that is the case, then the baseOp is X, and the memory operand immediate + // becomes the addition immediate. Otherwise baseOp keeps being the address. + int imm = GetConstOp(ref baseOp, type); + if (imm != 0) + { + return MemoryOp(type, baseOp, default, Multiplier.x1, imm); + } + + // Now we check if the baseOp is the result of a local Y with a local Z addition. + // If that is the case, we now set baseOp to Y and indexOp to Z. We further check + // if Z is the result of a left shift of local W by a value == 0 or == Log2(AccessSize), + // if that is the case, we set indexOp to W and adjust the scale value of the memory operand + // to match that of the left shift. + // There is one missed case, which is the address being a shift result, but this is + // probably not worth optimizing as it should never happen. + (Operand indexOp, Multiplier scale) = GetIndexOp(ref baseOp, type); + + // If baseOp is still equal to address, then there's nothing that can be optimized. + if (baseOp == addr) + { + return default; + } + + return MemoryOp(type, baseOp, indexOp, scale, 0); + } + + private static int GetConstOp(ref Operand baseOp, OperandType accessType) + { + Operation operation = GetAsgOpWithInst(baseOp, Instruction.Add); + + if (operation == default) + { + return 0; + } + + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + Operand constOp; + Operand otherOp; + + if (src1.Kind == OperandKind.Constant && src2.Kind == OperandKind.LocalVariable) + { + constOp = src1; + otherOp = src2; + } + else if (src1.Kind == OperandKind.LocalVariable && src2.Kind == OperandKind.Constant) + { + constOp = src2; + otherOp = src1; + } + else + { + return 0; + } + + // If we have addition by a constant that we can't encode on the instruction, + // then we can't optimize it further. + if (ConstTooLong(constOp, accessType)) + { + return 0; + } + + baseOp = otherOp; + + return constOp.AsInt32(); + } + + private static (Operand, Multiplier) GetIndexOp(ref Operand baseOp, OperandType accessType) + { + Operand indexOp = default; + + Multiplier scale = Multiplier.x1; + + Operation addOp = GetAsgOpWithInst(baseOp, Instruction.Add); + + if (addOp == default) + { + return (indexOp, scale); + } + + Operand src1 = addOp.GetSource(0); + Operand src2 = addOp.GetSource(1); + + if (src1.Kind != OperandKind.LocalVariable || src2.Kind != OperandKind.LocalVariable) + { + return (indexOp, scale); + } + + baseOp = src1; + indexOp = src2; + + Operation shlOp = GetAsgOpWithInst(src1, Instruction.ShiftLeft); + + bool indexOnSrc2 = false; + + if (shlOp == default) + { + shlOp = GetAsgOpWithInst(src2, Instruction.ShiftLeft); + + indexOnSrc2 = true; + } + + if (shlOp != default) + { + Operand shSrc = shlOp.GetSource(0); + Operand shift = shlOp.GetSource(1); + + int maxShift = Assembler.GetScaleForType(accessType); + + if (shSrc.Kind == OperandKind.LocalVariable && + shift.Kind == OperandKind.Constant && + (shift.Value == 0 || shift.Value == (ulong)maxShift)) + { + scale = shift.Value switch + { + 1 => Multiplier.x2, + 2 => Multiplier.x4, + 3 => Multiplier.x8, + 4 => Multiplier.x16, + _ => Multiplier.x1 + }; + + baseOp = indexOnSrc2 ? src1 : src2; + indexOp = shSrc; + } + } + + return (indexOp, scale); + } + + private static Operation GetAsgOpWithInst(Operand op, Instruction inst) + { + // If we have multiple assignments, folding is not safe + // as the value may be different depending on the + // control flow path. + if (op.AssignmentsCount != 1) + { + return default; + } + + Operation asgOp = op.Assignments[0]; + + if (asgOp.Instruction != inst) + { + return default; + } + + return asgOp; + } + + private static bool IsMemoryLoadOrStore(Instruction inst) + { + return inst == Instruction.Load || inst == Instruction.Store; + } + + private static bool ConstTooLong(Operand constOp, OperandType accessType) + { + if ((uint)constOp.Value != constOp.Value) + { + return true; + } + + return !CodeGenCommon.ConstFitsOnUImm12(constOp.AsInt32(), accessType); + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/ArmCondition.cs b/src/ARMeilleure/CodeGen/Arm64/ArmCondition.cs new file mode 100644 index 00000000..db27a810 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/ArmCondition.cs @@ -0,0 +1,47 @@ +using ARMeilleure.IntermediateRepresentation; +using System; + +namespace ARMeilleure.CodeGen.Arm64 +{ + enum ArmCondition + { + Eq = 0, + Ne = 1, + GeUn = 2, + LtUn = 3, + Mi = 4, + Pl = 5, + Vs = 6, + Vc = 7, + GtUn = 8, + LeUn = 9, + Ge = 10, + Lt = 11, + Gt = 12, + Le = 13, + Al = 14, + Nv = 15 + } + + static class ComparisonArm64Extensions + { + public static ArmCondition ToArmCondition(this Comparison comp) + { + return comp switch + { + Comparison.Equal => ArmCondition.Eq, + Comparison.NotEqual => ArmCondition.Ne, + Comparison.Greater => ArmCondition.Gt, + Comparison.LessOrEqual => ArmCondition.Le, + Comparison.GreaterUI => ArmCondition.GtUn, + Comparison.LessOrEqualUI => ArmCondition.LeUn, + Comparison.GreaterOrEqual => ArmCondition.Ge, + Comparison.Less => ArmCondition.Lt, + Comparison.GreaterOrEqualUI => ArmCondition.GeUn, + Comparison.LessUI => ArmCondition.LtUn, + + _ => throw new ArgumentException(null, nameof(comp)) + }; + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/ArmExtensionType.cs b/src/ARMeilleure/CodeGen/Arm64/ArmExtensionType.cs new file mode 100644 index 00000000..062a6d0b --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/ArmExtensionType.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.CodeGen.Arm64 +{ + enum ArmExtensionType + { + Uxtb = 0, + Uxth = 1, + Uxtw = 2, + Uxtx = 3, + Sxtb = 4, + Sxth = 5, + Sxtw = 6, + Sxtx = 7 + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/ArmShiftType.cs b/src/ARMeilleure/CodeGen/Arm64/ArmShiftType.cs new file mode 100644 index 00000000..d223a146 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/ArmShiftType.cs @@ -0,0 +1,11 @@ + +namespace ARMeilleure.CodeGen.Arm64 +{ + enum ArmShiftType + { + Lsl = 0, + Lsr = 1, + Asr = 2, + Ror = 3 + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/Arm64/Assembler.cs b/src/ARMeilleure/CodeGen/Arm64/Assembler.cs new file mode 100644 index 00000000..0ec0be7c --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/Assembler.cs @@ -0,0 +1,1160 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Diagnostics; +using System.IO; +using static ARMeilleure.IntermediateRepresentation.Operand; + +namespace ARMeilleure.CodeGen.Arm64 +{ + class Assembler + { + public const uint SfFlag = 1u << 31; + + private const int SpRegister = 31; + private const int ZrRegister = 31; + + private readonly Stream _stream; + + public Assembler(Stream stream) + { + _stream = stream; + } + + public void Add(Operand rd, Operand rn, Operand rm, ArmExtensionType extensionType, int shiftAmount = 0) + { + WriteInstructionAuto(0x0b200000u, rd, rn, rm, extensionType, shiftAmount); + } + + public void Add(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0, bool immForm = false) + { + WriteInstructionAuto(0x11000000u, 0x0b000000u, rd, rn, rm, shiftType, shiftAmount, immForm); + } + + public void And(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + WriteInstructionBitwiseAuto(0x12000000u, 0x0a000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public void Ands(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + WriteInstructionBitwiseAuto(0x72000000u, 0x6a000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public void Asr(Operand rd, Operand rn, Operand rm) + { + if (rm.Kind == OperandKind.Constant) + { + int shift = rm.AsInt32(); + int mask = rd.Type == OperandType.I64 ? 63 : 31; + shift &= mask; + Sbfm(rd, rn, shift, mask); + } + else + { + Asrv(rd, rn, rm); + } + } + + public void Asrv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionBitwiseAuto(0x1ac02800u, rd, rn, rm); + } + + public void B(int imm) + { + WriteUInt32(0x14000000u | EncodeSImm26_2(imm)); + } + + public void B(ArmCondition condition, int imm) + { + WriteUInt32(0x54000000u | (uint)condition | (EncodeSImm19_2(imm) << 5)); + } + + public void Blr(Operand rn) + { + WriteUInt32(0xd63f0000u | (EncodeReg(rn) << 5)); + } + + public void Br(Operand rn) + { + WriteUInt32(0xd61f0000u | (EncodeReg(rn) << 5)); + } + + public void Brk() + { + WriteUInt32(0xd4200000u); + } + + public void Cbz(Operand rt, int imm) + { + WriteInstructionAuto(0x34000000u | (EncodeSImm19_2(imm) << 5), rt); + } + + public void Cbnz(Operand rt, int imm) + { + WriteInstructionAuto(0x35000000u | (EncodeSImm19_2(imm) << 5), rt); + } + + public void Clrex(int crm = 15) + { + WriteUInt32(0xd503305fu | (EncodeUImm4(crm) << 8)); + } + + public void Clz(Operand rd, Operand rn) + { + WriteInstructionAuto(0x5ac01000u, rd, rn); + } + + public void CmeqVector(Operand rd, Operand rn, Operand rm, int size, bool q = true) + { + Debug.Assert((uint)size < 4); + WriteSimdInstruction(0x2e208c00u | ((uint)size << 22), rd, rn, rm, q); + } + + public void Cmp(Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + Subs(Factory.Register(ZrRegister, RegisterType.Integer, rn.Type), rn, rm, shiftType, shiftAmount); + } + + public void Csel(Operand rd, Operand rn, Operand rm, ArmCondition condition) + { + WriteInstructionBitwiseAuto(0x1a800000u | ((uint)condition << 12), rd, rn, rm); + } + + public void Cset(Operand rd, ArmCondition condition) + { + var zr = Factory.Register(ZrRegister, RegisterType.Integer, rd.Type); + Csinc(rd, zr, zr, (ArmCondition)((int)condition ^ 1)); + } + + public void Csinc(Operand rd, Operand rn, Operand rm, ArmCondition condition) + { + WriteInstructionBitwiseAuto(0x1a800400u | ((uint)condition << 12), rd, rn, rm); + } + + public void Dmb(uint option) + { + WriteUInt32(0xd50330bfu | (option << 8)); + } + + public void DupScalar(Operand rd, Operand rn, int index, int size) + { + WriteInstruction(0x5e000400u | (EncodeIndexSizeImm5(index, size) << 16), rd, rn); + } + + public void Eor(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + WriteInstructionBitwiseAuto(0x52000000u, 0x4a000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public void EorVector(Operand rd, Operand rn, Operand rm, bool q = true) + { + WriteSimdInstruction(0x2e201c00u, rd, rn, rm, q); + } + + public void Extr(Operand rd, Operand rn, Operand rm, int imms) + { + uint n = rd.Type == OperandType.I64 ? 1u << 22 : 0u; + WriteInstructionBitwiseAuto(0x13800000u | n | (EncodeUImm6(imms) << 10), rd, rn, rm); + } + + public void FaddScalar(Operand rd, Operand rn, Operand rm) + { + WriteFPInstructionAuto(0x1e202800u, rd, rn, rm); + } + + public void FcvtScalar(Operand rd, Operand rn) + { + uint instruction = 0x1e224000u | (rd.Type == OperandType.FP64 ? 1u << 15 : 1u << 22); + WriteUInt32(instruction | EncodeReg(rd) | (EncodeReg(rn) << 5)); + } + + public void FdivScalar(Operand rd, Operand rn, Operand rm) + { + WriteFPInstructionAuto(0x1e201800u, rd, rn, rm); + } + + public void Fmov(Operand rd, Operand rn) + { + WriteFPInstructionAuto(0x1e204000u, rd, rn); + } + + public void Fmov(Operand rd, Operand rn, bool topHalf) + { + Debug.Assert(rd.Type.IsInteger() != rn.Type.IsInteger()); + Debug.Assert(rd.Type == OperandType.I64 || rn.Type == OperandType.I64 || !topHalf); + + uint opcode = rd.Type.IsInteger() ? 0b110u : 0b111u; + + uint rmode = topHalf ? 1u << 19 : 0u; + uint ftype = rd.Type == OperandType.FP64 || rn.Type == OperandType.FP64 ? 1u << 22 : 0u; + uint sf = rd.Type == OperandType.I64 || rn.Type == OperandType.I64 ? SfFlag : 0u; + + WriteUInt32(0x1e260000u | (opcode << 16) | rmode | ftype | sf | EncodeReg(rd) | (EncodeReg(rn) << 5)); + } + + public void FmulScalar(Operand rd, Operand rn, Operand rm) + { + WriteFPInstructionAuto(0x1e200800u, rd, rn, rm); + } + + public void FnegScalar(Operand rd, Operand rn) + { + WriteFPInstructionAuto(0x1e214000u, rd, rn); + } + + public void FsubScalar(Operand rd, Operand rn, Operand rm) + { + WriteFPInstructionAuto(0x1e203800u, rd, rn, rm); + } + + public void Ins(Operand rd, Operand rn, int index, int size) + { + WriteInstruction(0x4e001c00u | (EncodeIndexSizeImm5(index, size) << 16), rd, rn); + } + + public void Ins(Operand rd, Operand rn, int srcIndex, int dstIndex, int size) + { + uint imm4 = (uint)srcIndex << size; + Debug.Assert((uint)srcIndex < (16u >> size)); + WriteInstruction(0x6e000400u | (imm4 << 11) | (EncodeIndexSizeImm5(dstIndex, size) << 16), rd, rn); + } + + public void Ldaxp(Operand rt, Operand rt2, Operand rn) + { + WriteInstruction(0x887f8000u | ((rt.Type == OperandType.I64 ? 3u : 2u) << 30), rt, rn, rt2); + } + + public void Ldaxr(Operand rt, Operand rn) + { + WriteInstruction(0x085ffc00u | ((rt.Type == OperandType.I64 ? 3u : 2u) << 30), rt, rn); + } + + public void Ldaxrb(Operand rt, Operand rn) + { + WriteInstruction(0x085ffc00u, rt, rn); + } + + public void Ldaxrh(Operand rt, Operand rn) + { + WriteInstruction(0x085ffc00u | (1u << 30), rt, rn); + } + + public void LdpRiPost(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x28c00000u, 0x2cc00000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public void LdpRiPre(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x29c00000u, 0x2dc00000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public void LdpRiUn(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x29400000u, 0x2d400000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public void Ldr(Operand rt, Operand rn) + { + if (rn.Kind == OperandKind.Memory) + { + MemoryOperand memOp = rn.GetMemory(); + + if (memOp.Index != default) + { + Debug.Assert(memOp.Displacement == 0); + Debug.Assert(memOp.Scale == Multiplier.x1 || (int)memOp.Scale == GetScaleForType(rt.Type)); + LdrRr(rt, memOp.BaseAddress, memOp.Index, ArmExtensionType.Uxtx, memOp.Scale != Multiplier.x1); + } + else + { + LdrRiUn(rt, memOp.BaseAddress, memOp.Displacement); + } + } + else + { + LdrRiUn(rt, rn, 0); + } + } + + public void LdrLit(Operand rt, int offset) + { + uint instruction = 0x18000000u | (EncodeSImm19_2(offset) << 5); + + if (rt.Type == OperandType.I64) + { + instruction |= 1u << 30; + } + + WriteInstruction(instruction, rt); + } + + public void LdrRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8400400u, 0x3c400400u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void LdrRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8400c00u, 0x3c400c00u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void LdrRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb9400000u, 0x3d400000u, rt.Type) | (EncodeUImm12(imm, rt.Type) << 10); + WriteInstruction(instruction, rt, rn); + } + + public void LdrRr(Operand rt, Operand rn, Operand rm, ArmExtensionType extensionType, bool shift) + { + uint instruction = GetLdrStrInstruction(0xb8600800u, 0x3ce00800u, rt.Type); + WriteInstructionLdrStrAuto(instruction, rt, rn, rm, extensionType, shift); + } + + public void LdrbRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = 0x38400400u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void LdrbRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = 0x38400c00u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void LdrbRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = 0x39400000u | (EncodeUImm12(imm, 0) << 10); + WriteInstruction(instruction, rt, rn); + } + + public void LdrhRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = 0x78400400u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void LdrhRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = 0x78400c00u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void LdrhRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = 0x79400000u | (EncodeUImm12(imm, 1) << 10); + WriteInstruction(instruction, rt, rn); + } + + public void Ldur(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8400000u, 0x3c400000u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void Lsl(Operand rd, Operand rn, Operand rm) + { + if (rm.Kind == OperandKind.Constant) + { + int shift = rm.AsInt32(); + int mask = rd.Type == OperandType.I64 ? 63 : 31; + shift &= mask; + Ubfm(rd, rn, -shift & mask, mask - shift); + } + else + { + Lslv(rd, rn, rm); + } + } + + public void Lslv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionBitwiseAuto(0x1ac02000u, rd, rn, rm); + } + + public void Lsr(Operand rd, Operand rn, Operand rm) + { + if (rm.Kind == OperandKind.Constant) + { + int shift = rm.AsInt32(); + int mask = rd.Type == OperandType.I64 ? 63 : 31; + shift &= mask; + Ubfm(rd, rn, shift, mask); + } + else + { + Lsrv(rd, rn, rm); + } + } + + public void Lsrv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionBitwiseAuto(0x1ac02400u, rd, rn, rm); + } + + public void Madd(Operand rd, Operand rn, Operand rm, Operand ra) + { + WriteInstructionAuto(0x1b000000u, rd, rn, rm, ra); + } + + public void Mul(Operand rd, Operand rn, Operand rm) + { + Madd(rd, rn, rm, Factory.Register(ZrRegister, RegisterType.Integer, rd.Type)); + } + + public void Mov(Operand rd, Operand rn) + { + if (rd.Type.IsInteger()) + { + Orr(rd, Factory.Register(ZrRegister, RegisterType.Integer, rd.Type), rn); + } + else + { + OrrVector(rd, rn, rn); + } + } + + public void MovSp(Operand rd, Operand rn) + { + if (rd.GetRegister().Index == SpRegister || + rn.GetRegister().Index == SpRegister) + { + Add(rd, rn, Factory.Const(rd.Type, 0), immForm: true); + } + else + { + Mov(rd, rn); + } + } + + public void Mov(Operand rd, int imm) + { + Movz(rd, imm, 0); + } + + public void Movz(Operand rd, int imm, int hw) + { + Debug.Assert((hw & (rd.Type == OperandType.I64 ? 3 : 1)) == hw); + WriteInstructionAuto(0x52800000u | (EncodeUImm16(imm) << 5) | ((uint)hw << 21), rd); + } + + public void Movk(Operand rd, int imm, int hw) + { + Debug.Assert((hw & (rd.Type == OperandType.I64 ? 3 : 1)) == hw); + WriteInstructionAuto(0x72800000u | (EncodeUImm16(imm) << 5) | ((uint)hw << 21), rd); + } + + public void Mrs(Operand rt, uint o0, uint op1, uint crn, uint crm, uint op2) + { + uint instruction = 0xd5300000u; + + instruction |= (op2 & 7) << 5; + instruction |= (crm & 15) << 8; + instruction |= (crn & 15) << 12; + instruction |= (op1 & 7) << 16; + instruction |= (o0 & 1) << 19; + + WriteInstruction(instruction, rt); + } + + public void Mvn(Operand rd, Operand rn, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + Orn(rd, Factory.Register(ZrRegister, RegisterType.Integer, rd.Type), rn, shiftType, shiftAmount); + } + + public void Neg(Operand rd, Operand rn, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + Sub(rd, Factory.Register(ZrRegister, RegisterType.Integer, rd.Type), rn, shiftType, shiftAmount); + } + + public void Orn(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + WriteInstructionBitwiseAuto(0x2a200000u, rd, rn, rm, shiftType, shiftAmount); + } + + public void Orr(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + WriteInstructionBitwiseAuto(0x32000000u, 0x2a000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public void OrrVector(Operand rd, Operand rn, Operand rm, bool q = true) + { + WriteSimdInstruction(0x0ea01c00u, rd, rn, rm, q); + } + + public void Ret(Operand rn) + { + WriteUInt32(0xd65f0000u | (EncodeReg(rn) << 5)); + } + + public void Rev(Operand rd, Operand rn) + { + uint opc0 = rd.Type == OperandType.I64 ? 1u << 10 : 0u; + WriteInstructionAuto(0x5ac00800u | opc0, rd, rn); + } + + public void Ror(Operand rd, Operand rn, Operand rm) + { + if (rm.Kind == OperandKind.Constant) + { + int shift = rm.AsInt32(); + int mask = rd.Type == OperandType.I64 ? 63 : 31; + shift &= mask; + Extr(rd, rn, rn, shift); + } + else + { + Rorv(rd, rn, rm); + } + } + + public void Rorv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionBitwiseAuto(0x1ac02c00u, rd, rn, rm); + } + + public void Sbfm(Operand rd, Operand rn, int immr, int imms) + { + uint n = rd.Type == OperandType.I64 ? 1u << 22 : 0u; + WriteInstructionAuto(0x13000000u | n | (EncodeUImm6(imms) << 10) | (EncodeUImm6(immr) << 16), rd, rn); + } + + public void ScvtfScalar(Operand rd, Operand rn) + { + uint instruction = 0x1e220000u; + + if (rn.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + WriteFPInstructionAuto(instruction, rd, rn); + } + + public void Sdiv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16Auto(0x1ac00c00u, rd, rn, rm); + } + + public void Smulh(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x9b407c00u, rd, rn, rm); + } + + public void Stlxp(Operand rt, Operand rt2, Operand rn, Operand rs) + { + WriteInstruction(0x88208000u | ((rt.Type == OperandType.I64 ? 3u : 2u) << 30), rt, rn, rs, rt2); + } + + public void Stlxr(Operand rt, Operand rn, Operand rs) + { + WriteInstructionRm16(0x0800fc00u | ((rt.Type == OperandType.I64 ? 3u : 2u) << 30), rt, rn, rs); + } + + public void Stlxrb(Operand rt, Operand rn, Operand rs) + { + WriteInstructionRm16(0x0800fc00u, rt, rn, rs); + } + + public void Stlxrh(Operand rt, Operand rn, Operand rs) + { + WriteInstructionRm16(0x0800fc00u | (1u << 30), rt, rn, rs); + } + + public void StpRiPost(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x28800000u, 0x2c800000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public void StpRiPre(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x29800000u, 0x2d800000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public void StpRiUn(Operand rt, Operand rt2, Operand rn, int imm) + { + uint instruction = GetLdpStpInstruction(0x29000000u, 0x2d000000u, imm, rt.Type); + WriteInstruction(instruction, rt, rn, rt2); + } + + public void Str(Operand rt, Operand rn) + { + if (rn.Kind == OperandKind.Memory) + { + MemoryOperand memOp = rn.GetMemory(); + + if (memOp.Index != default) + { + Debug.Assert(memOp.Displacement == 0); + Debug.Assert(memOp.Scale == Multiplier.x1 || (int)memOp.Scale == GetScaleForType(rt.Type)); + StrRr(rt, memOp.BaseAddress, memOp.Index, ArmExtensionType.Uxtx, memOp.Scale != Multiplier.x1); + } + else + { + StrRiUn(rt, memOp.BaseAddress, memOp.Displacement); + } + } + else + { + StrRiUn(rt, rn, 0); + } + } + + public void StrRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8000400u, 0x3c000400u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void StrRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8000c00u, 0x3c000c00u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void StrRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb9000000u, 0x3d000000u, rt.Type) | (EncodeUImm12(imm, rt.Type) << 10); + WriteInstruction(instruction, rt, rn); + } + + public void StrRr(Operand rt, Operand rn, Operand rm, ArmExtensionType extensionType, bool shift) + { + uint instruction = GetLdrStrInstruction(0xb8200800u, 0x3ca00800u, rt.Type); + WriteInstructionLdrStrAuto(instruction, rt, rn, rm, extensionType, shift); + } + + public void StrbRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = 0x38000400u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void StrbRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = 0x38000c00u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void StrbRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = 0x39000000u | (EncodeUImm12(imm, 0) << 10); + WriteInstruction(instruction, rt, rn); + } + + public void StrhRiPost(Operand rt, Operand rn, int imm) + { + uint instruction = 0x78000400u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void StrhRiPre(Operand rt, Operand rn, int imm) + { + uint instruction = 0x78000c00u | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void StrhRiUn(Operand rt, Operand rn, int imm) + { + uint instruction = 0x79000000u | (EncodeUImm12(imm, 1) << 10); + WriteInstruction(instruction, rt, rn); + } + + public void Stur(Operand rt, Operand rn, int imm) + { + uint instruction = GetLdrStrInstruction(0xb8000000u, 0x3c000000u, rt.Type) | (EncodeSImm9(imm) << 12); + WriteInstruction(instruction, rt, rn); + } + + public void Sub(Operand rd, Operand rn, Operand rm, ArmExtensionType extensionType, int shiftAmount = 0) + { + WriteInstructionAuto(0x4b200000u, rd, rn, rm, extensionType, shiftAmount); + } + + public void Sub(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + WriteInstructionAuto(0x51000000u, 0x4b000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public void Subs(Operand rd, Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + WriteInstructionAuto(0x71000000u, 0x6b000000u, rd, rn, rm, shiftType, shiftAmount); + } + + public void Sxtb(Operand rd, Operand rn) + { + Sbfm(rd, rn, 0, 7); + } + + public void Sxth(Operand rd, Operand rn) + { + Sbfm(rd, rn, 0, 15); + } + + public void Sxtw(Operand rd, Operand rn) + { + Sbfm(rd, rn, 0, 31); + } + + public void Tst(Operand rn, Operand rm, ArmShiftType shiftType = ArmShiftType.Lsl, int shiftAmount = 0) + { + Ands(Factory.Register(ZrRegister, RegisterType.Integer, rn.Type), rn, rm, shiftType, shiftAmount); + } + + public void Ubfm(Operand rd, Operand rn, int immr, int imms) + { + uint n = rd.Type == OperandType.I64 ? 1u << 22 : 0u; + WriteInstructionAuto(0x53000000u | n | (EncodeUImm6(imms) << 10) | (EncodeUImm6(immr) << 16), rd, rn); + } + + public void UcvtfScalar(Operand rd, Operand rn) + { + uint instruction = 0x1e230000u; + + if (rn.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + WriteFPInstructionAuto(instruction, rd, rn); + } + + public void Udiv(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16Auto(0x1ac00800u, rd, rn, rm); + } + + public void Umov(Operand rd, Operand rn, int index, int size) + { + uint q = size == 3 ? 1u << 30 : 0u; + WriteInstruction(0x0e003c00u | (EncodeIndexSizeImm5(index, size) << 16) | q, rd, rn); + } + + public void Umulh(Operand rd, Operand rn, Operand rm) + { + WriteInstructionRm16(0x9bc07c00u, rd, rn, rm); + } + + public void Uxtb(Operand rd, Operand rn) + { + Ubfm(rd, rn, 0, 7); + } + + public void Uxth(Operand rd, Operand rn) + { + Ubfm(rd, rn, 0, 15); + } + + private void WriteInstructionAuto( + uint instI, + uint instR, + Operand rd, + Operand rn, + Operand rm, + ArmShiftType shiftType = ArmShiftType.Lsl, + int shiftAmount = 0, + bool immForm = false) + { + if (rm.Kind == OperandKind.Constant && (rm.Value != 0 || immForm)) + { + Debug.Assert(shiftAmount == 0); + int imm = rm.AsInt32(); + Debug.Assert((uint)imm == rm.Value); + if (imm != 0 && (imm & 0xfff) == 0) + { + instI |= 1 << 22; // sh flag + imm >>= 12; + } + WriteInstructionAuto(instI | (EncodeUImm12(imm, 0) << 10), rd, rn); + } + else + { + instR |= EncodeUImm6(shiftAmount) << 10; + instR |= (uint)shiftType << 22; + + WriteInstructionRm16Auto(instR, rd, rn, rm); + } + } + + private void WriteInstructionAuto( + uint instruction, + Operand rd, + Operand rn, + Operand rm, + ArmExtensionType extensionType, + int shiftAmount = 0) + { + Debug.Assert((uint)shiftAmount <= 4); + + instruction |= (uint)shiftAmount << 10; + instruction |= (uint)extensionType << 13; + + WriteInstructionRm16Auto(instruction, rd, rn, rm); + } + + private void WriteInstructionBitwiseAuto( + uint instI, + uint instR, + Operand rd, + Operand rn, + Operand rm, + ArmShiftType shiftType = ArmShiftType.Lsl, + int shiftAmount = 0) + { + if (rm.Kind == OperandKind.Constant && rm.Value != 0) + { + Debug.Assert(shiftAmount == 0); + bool canEncode = CodeGenCommon.TryEncodeBitMask(rm, out int immN, out int immS, out int immR); + Debug.Assert(canEncode); + uint instruction = instI | ((uint)immS << 10) | ((uint)immR << 16) | ((uint)immN << 22); + + WriteInstructionAuto(instruction, rd, rn); + } + else + { + WriteInstructionBitwiseAuto(instR, rd, rn, rm, shiftType, shiftAmount); + } + } + + private void WriteInstructionBitwiseAuto( + uint instruction, + Operand rd, + Operand rn, + Operand rm, + ArmShiftType shiftType = ArmShiftType.Lsl, + int shiftAmount = 0) + { + if (rd.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + instruction |= EncodeUImm6(shiftAmount) << 10; + instruction |= (uint)shiftType << 22; + + WriteInstructionRm16(instruction, rd, rn, rm); + } + + private void WriteInstructionLdrStrAuto( + uint instruction, + Operand rd, + Operand rn, + Operand rm, + ArmExtensionType extensionType, + bool shift) + { + if (shift) + { + instruction |= 1u << 12; + } + + instruction |= (uint)extensionType << 13; + + if (rd.Type == OperandType.I64) + { + instruction |= 1u << 30; + } + + WriteInstructionRm16(instruction, rd, rn, rm); + } + + private void WriteInstructionAuto(uint instruction, Operand rd) + { + if (rd.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + WriteInstruction(instruction, rd); + } + + public void WriteInstructionAuto(uint instruction, Operand rd, Operand rn) + { + if (rd.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + WriteInstruction(instruction, rd, rn); + } + + private void WriteInstructionAuto(uint instruction, Operand rd, Operand rn, Operand rm, Operand ra) + { + if (rd.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + WriteInstruction(instruction, rd, rn, rm, ra); + } + + public void WriteInstruction(uint instruction, Operand rd) + { + WriteUInt32(instruction | EncodeReg(rd)); + } + + public void WriteInstruction(uint instruction, Operand rd, Operand rn) + { + WriteUInt32(instruction | EncodeReg(rd) | (EncodeReg(rn) << 5)); + } + + public void WriteInstruction(uint instruction, Operand rd, Operand rn, Operand rm) + { + WriteUInt32(instruction | EncodeReg(rd) | (EncodeReg(rn) << 5) | (EncodeReg(rm) << 10)); + } + + public void WriteInstruction(uint instruction, Operand rd, Operand rn, Operand rm, Operand ra) + { + WriteUInt32(instruction | EncodeReg(rd) | (EncodeReg(rn) << 5) | (EncodeReg(ra) << 10) | (EncodeReg(rm) << 16)); + } + + private void WriteFPInstructionAuto(uint instruction, Operand rd, Operand rn) + { + if (rd.Type == OperandType.FP64) + { + instruction |= 1u << 22; + } + + WriteUInt32(instruction | EncodeReg(rd) | (EncodeReg(rn) << 5)); + } + + private void WriteFPInstructionAuto(uint instruction, Operand rd, Operand rn, Operand rm) + { + if (rd.Type == OperandType.FP64) + { + instruction |= 1u << 22; + } + + WriteInstructionRm16(instruction, rd, rn, rm); + } + + private void WriteSimdInstruction(uint instruction, Operand rd, Operand rn, Operand rm, bool q = true) + { + if (q) + { + instruction |= 1u << 30; + } + + WriteInstructionRm16(instruction, rd, rn, rm); + } + + private void WriteInstructionRm16Auto(uint instruction, Operand rd, Operand rn, Operand rm) + { + if (rd.Type == OperandType.I64) + { + instruction |= SfFlag; + } + + WriteInstructionRm16(instruction, rd, rn, rm); + } + + public void WriteInstructionRm16(uint instruction, Operand rd, Operand rn, Operand rm) + { + WriteUInt32(instruction | EncodeReg(rd) | (EncodeReg(rn) << 5) | (EncodeReg(rm) << 16)); + } + + public void WriteInstructionRm16NoRet(uint instruction, Operand rn, Operand rm) + { + WriteUInt32(instruction | (EncodeReg(rn) << 5) | (EncodeReg(rm) << 16)); + } + + private static uint GetLdpStpInstruction(uint intInst, uint vecInst, int imm, OperandType type) + { + uint instruction; + int scale; + + if (type.IsInteger()) + { + instruction = intInst; + + if (type == OperandType.I64) + { + instruction |= SfFlag; + scale = 3; + } + else + { + scale = 2; + } + } + else + { + int opc = type switch + { + OperandType.FP32 => 0, + OperandType.FP64 => 1, + _ => 2 + }; + + instruction = vecInst | ((uint)opc << 30); + scale = 2 + opc; + } + + instruction |= (EncodeSImm7(imm, scale) << 15); + + return instruction; + } + + private static uint GetLdrStrInstruction(uint intInst, uint vecInst, OperandType type) + { + uint instruction; + + if (type.IsInteger()) + { + instruction = intInst; + + if (type == OperandType.I64) + { + instruction |= 1 << 30; + } + } + else + { + instruction = vecInst; + + if (type == OperandType.V128) + { + instruction |= 1u << 23; + } + else + { + instruction |= type == OperandType.FP32 ? 2u << 30 : 3u << 30; + } + } + + return instruction; + } + + private static uint EncodeIndexSizeImm5(int index, int size) + { + Debug.Assert((uint)size < 4); + Debug.Assert((uint)index < (16u >> size), $"Invalid index {index} and size {size} combination."); + return ((uint)index << (size + 1)) | (1u << size); + } + + private static uint EncodeSImm7(int value, int scale) + { + uint imm = (uint)(value >> scale) & 0x7f; + Debug.Assert(((int)imm << 25) >> (25 - scale) == value, $"Failed to encode constant 0x{value:X} with scale {scale}."); + return imm; + } + + private static uint EncodeSImm9(int value) + { + uint imm = (uint)value & 0x1ff; + Debug.Assert(((int)imm << 23) >> 23 == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeSImm19_2(int value) + { + uint imm = (uint)(value >> 2) & 0x7ffff; + Debug.Assert(((int)imm << 13) >> 11 == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeSImm26_2(int value) + { + uint imm = (uint)(value >> 2) & 0x3ffffff; + Debug.Assert(((int)imm << 6) >> 4 == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeUImm4(int value) + { + uint imm = (uint)value & 0xf; + Debug.Assert((int)imm == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeUImm6(int value) + { + uint imm = (uint)value & 0x3f; + Debug.Assert((int)imm == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeUImm12(int value, OperandType type) + { + return EncodeUImm12(value, GetScaleForType(type)); + } + + private static uint EncodeUImm12(int value, int scale) + { + uint imm = (uint)(value >> scale) & 0xfff; + Debug.Assert((int)imm << scale == value, $"Failed to encode constant 0x{value:X} with scale {scale}."); + return imm; + } + + private static uint EncodeUImm16(int value) + { + uint imm = (uint)value & 0xffff; + Debug.Assert((int)imm == value, $"Failed to encode constant 0x{value:X}."); + return imm; + } + + private static uint EncodeReg(Operand reg) + { + if (reg.Kind == OperandKind.Constant && reg.Value == 0) + { + return ZrRegister; + } + + uint regIndex = (uint)reg.GetRegister().Index; + Debug.Assert(reg.Kind == OperandKind.Register); + Debug.Assert(regIndex < 32); + return regIndex; + } + + public static int GetScaleForType(OperandType type) + { + return type switch + { + OperandType.I32 => 2, + OperandType.I64 => 3, + OperandType.FP32 => 2, + OperandType.FP64 => 3, + OperandType.V128 => 4, + _ => throw new ArgumentException($"Invalid type {type}.") + }; + } + + private void WriteInt16(short value) + { + WriteUInt16((ushort)value); + } + + private void WriteInt32(int value) + { + WriteUInt32((uint)value); + } + + private void WriteByte(byte value) + { + _stream.WriteByte(value); + } + + private void WriteUInt16(ushort value) + { + _stream.WriteByte((byte)(value >> 0)); + _stream.WriteByte((byte)(value >> 8)); + } + + private void WriteUInt32(uint value) + { + _stream.WriteByte((byte)(value >> 0)); + _stream.WriteByte((byte)(value >> 8)); + _stream.WriteByte((byte)(value >> 16)); + _stream.WriteByte((byte)(value >> 24)); + } + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/CallingConvention.cs b/src/ARMeilleure/CodeGen/Arm64/CallingConvention.cs new file mode 100644 index 00000000..fda8d786 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/CallingConvention.cs @@ -0,0 +1,96 @@ +using System; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static class CallingConvention + { + private const int RegistersMask = unchecked((int)0xffffffff); + + // Some of those register have specific roles and can't be used as general purpose registers. + // X18 - Reserved for platform specific usage. + // X29 - Frame pointer. + // X30 - Return address. + // X31 - Not an actual register, in some cases maps to SP, and in others to ZR. + private const int ReservedRegsMask = (1 << CodeGenCommon.ReservedRegister) | (1 << 18) | (1 << 29) | (1 << 30) | (1 << 31); + + public static int GetIntAvailableRegisters() + { + return RegistersMask & ~ReservedRegsMask; + } + + public static int GetVecAvailableRegisters() + { + return RegistersMask; + } + + public static int GetIntCallerSavedRegisters() + { + return (GetIntCalleeSavedRegisters() ^ RegistersMask) & ~ReservedRegsMask; + } + + public static int GetFpCallerSavedRegisters() + { + return GetFpCalleeSavedRegisters() ^ RegistersMask; + } + + public static int GetVecCallerSavedRegisters() + { + return GetVecCalleeSavedRegisters() ^ RegistersMask; + } + + public static int GetIntCalleeSavedRegisters() + { + return 0x1ff80000; // X19 to X28 + } + + public static int GetFpCalleeSavedRegisters() + { + return 0xff00; // D8 to D15 + } + + public static int GetVecCalleeSavedRegisters() + { + return 0; + } + + public static int GetArgumentsOnRegsCount() + { + return 8; + } + + public static int GetIntArgumentRegister(int index) + { + if ((uint)index < (uint)GetArgumentsOnRegsCount()) + { + return index; + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + + public static int GetVecArgumentRegister(int index) + { + if ((uint)index < (uint)GetArgumentsOnRegsCount()) + { + return index; + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + + public static int GetIntReturnRegister() + { + return 0; + } + + public static int GetIntReturnRegisterHigh() + { + return 1; + } + + public static int GetVecReturnRegister() + { + return 0; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/Arm64/CodeGenCommon.cs b/src/ARMeilleure/CodeGen/Arm64/CodeGenCommon.cs new file mode 100644 index 00000000..8d1e597b --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/CodeGenCommon.cs @@ -0,0 +1,91 @@ +using ARMeilleure.IntermediateRepresentation; +using System.Numerics; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static class CodeGenCommon + { + public const int TcAddressRegister = 8; + public const int ReservedRegister = 17; + + public static bool ConstFitsOnSImm7(int value, int scale) + { + return (((value >> scale) << 25) >> (25 - scale)) == value; + } + + public static bool ConstFitsOnSImm9(int value) + { + return ((value << 23) >> 23) == value; + } + + public static bool ConstFitsOnUImm12(int value) + { + return (value & 0xfff) == value; + } + + public static bool ConstFitsOnUImm12(int value, OperandType type) + { + int scale = Assembler.GetScaleForType(type); + return (((value >> scale) & 0xfff) << scale) == value; + } + + public static bool TryEncodeBitMask(Operand operand, out int immN, out int immS, out int immR) + { + return TryEncodeBitMask(operand.Type, operand.Value, out immN, out immS, out immR); + } + + public static bool TryEncodeBitMask(OperandType type, ulong value, out int immN, out int immS, out int immR) + { + if (type == OperandType.I32) + { + value |= value << 32; + } + + return TryEncodeBitMask(value, out immN, out immS, out immR); + } + + public static bool TryEncodeBitMask(ulong value, out int immN, out int immS, out int immR) + { + // Some special values also can't be encoded: + // 0 can't be encoded because we need to subtract 1 from onesCount (which would became negative if 0). + // A value with all bits set can't be encoded because it is reserved according to the spec, because: + // Any value AND all ones will be equal itself, so it's effectively a no-op. + // Any value OR all ones will be equal all ones, so one can just use MOV. + // Any value XOR all ones will be equal its inverse, so one can just use MVN. + if (value == 0 || value == ulong.MaxValue) + { + immN = 0; + immS = 0; + immR = 0; + + return false; + } + + // Normalize value, rotating it such that the LSB is 1: Ensures we get a complete element that has not + // been cut-in-half across the word boundary. + int rotation = BitOperations.TrailingZeroCount(value & (value + 1)); + ulong rotatedValue = ulong.RotateRight(value, rotation); + + // Now that we have a complete element in the LSB with the LSB = 1, determine size and number of ones + // in element. + int elementSize = BitOperations.TrailingZeroCount(rotatedValue & (rotatedValue + 1)); + int onesInElement = BitOperations.TrailingZeroCount(~rotatedValue); + + // Check the value is repeating; also ensures element size is a power of two. + if (ulong.RotateRight(value, elementSize) != value) + { + immN = 0; + immS = 0; + immR = 0; + + return false; + } + + immN = (elementSize >> 6) & 1; + immS = (((~elementSize + 1) << 1) | (onesInElement - 1)) & 0x3f; + immR = (elementSize - rotation) & (elementSize - 1); + + return true; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs b/src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs new file mode 100644 index 00000000..0dd5355f --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs @@ -0,0 +1,287 @@ +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.CodeGen.RegisterAllocators; +using ARMeilleure.IntermediateRepresentation; +using Ryujinx.Common.Memory; +using System; +using System.Collections.Generic; +using System.IO; + +namespace ARMeilleure.CodeGen.Arm64 +{ + class CodeGenContext + { + private const int BccInstLength = 4; + private const int CbnzInstLength = 4; + private const int LdrLitInstLength = 4; + + private Stream _stream; + + public int StreamOffset => (int)_stream.Length; + + public AllocationResult AllocResult { get; } + + public Assembler Assembler { get; } + + public BasicBlock CurrBlock { get; private set; } + + public bool HasCall { get; } + + public int CallArgsRegionSize { get; } + public int FpLrSaveRegionSize { get; } + + private readonly Dictionary _visitedBlocks; + private readonly Dictionary> _pendingBranches; + + private struct ConstantPoolEntry + { + public readonly int Offset; + public readonly Symbol Symbol; + public readonly List<(Operand, int)> LdrOffsets; + + public ConstantPoolEntry(int offset, Symbol symbol) + { + Offset = offset; + Symbol = symbol; + LdrOffsets = new List<(Operand, int)>(); + } + } + + private readonly Dictionary _constantPool; + + private bool _constantPoolWritten; + private long _constantPoolOffset; + + private ArmCondition _jNearCondition; + private Operand _jNearValue; + + private long _jNearPosition; + + private readonly bool _relocatable; + + public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable) + { + _stream = MemoryStreamManager.Shared.GetStream(); + + AllocResult = allocResult; + + Assembler = new Assembler(_stream); + + bool hasCall = maxCallArgs >= 0; + + HasCall = hasCall; + + if (maxCallArgs < 0) + { + maxCallArgs = 0; + } + + CallArgsRegionSize = maxCallArgs * 16; + FpLrSaveRegionSize = hasCall ? 16 : 0; + + _visitedBlocks = new Dictionary(); + _pendingBranches = new Dictionary>(); + _constantPool = new Dictionary(); + + _relocatable = relocatable; + } + + public void EnterBlock(BasicBlock block) + { + CurrBlock = block; + + long target = _stream.Position; + + if (_pendingBranches.TryGetValue(block, out var list)) + { + foreach (var tuple in list) + { + _stream.Seek(tuple.BranchPos, SeekOrigin.Begin); + WriteBranch(tuple.Condition, target); + } + + _stream.Seek(target, SeekOrigin.Begin); + _pendingBranches.Remove(block); + } + + _visitedBlocks.Add(block, target); + } + + public void JumpTo(BasicBlock target) + { + JumpTo(ArmCondition.Al, target); + } + + public void JumpTo(ArmCondition condition, BasicBlock target) + { + if (_visitedBlocks.TryGetValue(target, out long offset)) + { + WriteBranch(condition, offset); + } + else + { + if (!_pendingBranches.TryGetValue(target, out var list)) + { + list = new List<(ArmCondition, long)>(); + _pendingBranches.Add(target, list); + } + + list.Add((condition, _stream.Position)); + + _stream.Seek(BccInstLength, SeekOrigin.Current); + } + } + + private void WriteBranch(ArmCondition condition, long to) + { + int imm = checked((int)(to - _stream.Position)); + + if (condition != ArmCondition.Al) + { + Assembler.B(condition, imm); + } + else + { + Assembler.B(imm); + } + } + + public void JumpToNear(ArmCondition condition) + { + _jNearCondition = condition; + _jNearPosition = _stream.Position; + + _stream.Seek(BccInstLength, SeekOrigin.Current); + } + + public void JumpToNearIfNotZero(Operand value) + { + _jNearValue = value; + _jNearPosition = _stream.Position; + + _stream.Seek(CbnzInstLength, SeekOrigin.Current); + } + + public void JumpHere() + { + long currentPosition = _stream.Position; + long offset = currentPosition - _jNearPosition; + + _stream.Seek(_jNearPosition, SeekOrigin.Begin); + + if (_jNearValue != default) + { + Assembler.Cbnz(_jNearValue, checked((int)offset)); + _jNearValue = default; + } + else + { + Assembler.B(_jNearCondition, checked((int)offset)); + } + + _stream.Seek(currentPosition, SeekOrigin.Begin); + } + + public void ReserveRelocatableConstant(Operand rt, Symbol symbol, ulong value) + { + if (!_constantPool.TryGetValue(value, out ConstantPoolEntry cpe)) + { + cpe = new ConstantPoolEntry(_constantPool.Count * sizeof(ulong), symbol); + _constantPool.Add(value, cpe); + } + + cpe.LdrOffsets.Add((rt, (int)_stream.Position)); + _stream.Seek(LdrLitInstLength, SeekOrigin.Current); + } + + private long WriteConstantPool() + { + if (_constantPoolWritten) + { + return _constantPoolOffset; + } + + long constantPoolBaseOffset = _stream.Position; + + foreach (ulong value in _constantPool.Keys) + { + WriteUInt64(value); + } + + foreach (ConstantPoolEntry cpe in _constantPool.Values) + { + foreach ((Operand rt, int ldrOffset) in cpe.LdrOffsets) + { + _stream.Seek(ldrOffset, SeekOrigin.Begin); + + int absoluteOffset = checked((int)(constantPoolBaseOffset + cpe.Offset)); + int pcRelativeOffset = absoluteOffset - ldrOffset; + + Assembler.LdrLit(rt, pcRelativeOffset); + } + } + + _stream.Seek(constantPoolBaseOffset + _constantPool.Count * sizeof(ulong), SeekOrigin.Begin); + + _constantPoolOffset = constantPoolBaseOffset; + _constantPoolWritten = true; + + return constantPoolBaseOffset; + } + + public (byte[], RelocInfo) GetCode() + { + long constantPoolBaseOffset = WriteConstantPool(); + + byte[] code = new byte[_stream.Length]; + + long originalPosition = _stream.Position; + + _stream.Seek(0, SeekOrigin.Begin); + _stream.Read(code, 0, code.Length); + _stream.Seek(originalPosition, SeekOrigin.Begin); + + RelocInfo relocInfo; + + if (_relocatable) + { + RelocEntry[] relocs = new RelocEntry[_constantPool.Count]; + + int index = 0; + + foreach (ConstantPoolEntry cpe in _constantPool.Values) + { + if (cpe.Symbol.Type != SymbolType.None) + { + int absoluteOffset = checked((int)(constantPoolBaseOffset + cpe.Offset)); + relocs[index++] = new RelocEntry(absoluteOffset, cpe.Symbol); + } + } + + if (index != relocs.Length) + { + Array.Resize(ref relocs, index); + } + + relocInfo = new RelocInfo(relocs); + } + else + { + relocInfo = new RelocInfo(Array.Empty()); + } + + return (code, relocInfo); + } + + private void WriteUInt64(ulong value) + { + _stream.WriteByte((byte)(value >> 0)); + _stream.WriteByte((byte)(value >> 8)); + _stream.WriteByte((byte)(value >> 16)); + _stream.WriteByte((byte)(value >> 24)); + _stream.WriteByte((byte)(value >> 32)); + _stream.WriteByte((byte)(value >> 40)); + _stream.WriteByte((byte)(value >> 48)); + _stream.WriteByte((byte)(value >> 56)); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/Arm64/CodeGenerator.cs b/src/ARMeilleure/CodeGen/Arm64/CodeGenerator.cs new file mode 100644 index 00000000..fc4fa976 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/CodeGenerator.cs @@ -0,0 +1,1580 @@ +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.CodeGen.Optimizations; +using ARMeilleure.CodeGen.RegisterAllocators; +using ARMeilleure.CodeGen.Unwinding; +using ARMeilleure.Common; +using ARMeilleure.Diagnostics; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Numerics; + +using static ARMeilleure.IntermediateRepresentation.Operand; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static class CodeGenerator + { + private const int DWordScale = 3; + + private const int RegistersCount = 32; + + private const int FpRegister = 29; + private const int LrRegister = 30; + private const int SpRegister = 31; + private const int ZrRegister = 31; + + private enum AccessSize + { + Byte, + Hword, + Auto + } + + private static Action[] _instTable; + + static CodeGenerator() + { + _instTable = new Action[EnumUtils.GetCount(typeof(Instruction))]; + + Add(Instruction.Add, GenerateAdd); + Add(Instruction.BitwiseAnd, GenerateBitwiseAnd); + Add(Instruction.BitwiseExclusiveOr, GenerateBitwiseExclusiveOr); + Add(Instruction.BitwiseNot, GenerateBitwiseNot); + Add(Instruction.BitwiseOr, GenerateBitwiseOr); + Add(Instruction.BranchIf, GenerateBranchIf); + Add(Instruction.ByteSwap, GenerateByteSwap); + Add(Instruction.Call, GenerateCall); + //Add(Instruction.Clobber, GenerateClobber); + Add(Instruction.Compare, GenerateCompare); + Add(Instruction.CompareAndSwap, GenerateCompareAndSwap); + Add(Instruction.CompareAndSwap16, GenerateCompareAndSwap16); + Add(Instruction.CompareAndSwap8, GenerateCompareAndSwap8); + Add(Instruction.ConditionalSelect, GenerateConditionalSelect); + Add(Instruction.ConvertI64ToI32, GenerateConvertI64ToI32); + Add(Instruction.ConvertToFP, GenerateConvertToFP); + Add(Instruction.ConvertToFPUI, GenerateConvertToFPUI); + Add(Instruction.Copy, GenerateCopy); + Add(Instruction.CountLeadingZeros, GenerateCountLeadingZeros); + Add(Instruction.Divide, GenerateDivide); + Add(Instruction.DivideUI, GenerateDivideUI); + Add(Instruction.Fill, GenerateFill); + Add(Instruction.Load, GenerateLoad); + Add(Instruction.Load16, GenerateLoad16); + Add(Instruction.Load8, GenerateLoad8); + Add(Instruction.MemoryBarrier, GenerateMemoryBarrier); + Add(Instruction.Multiply, GenerateMultiply); + Add(Instruction.Multiply64HighSI, GenerateMultiply64HighSI); + Add(Instruction.Multiply64HighUI, GenerateMultiply64HighUI); + Add(Instruction.Negate, GenerateNegate); + Add(Instruction.Return, GenerateReturn); + Add(Instruction.RotateRight, GenerateRotateRight); + Add(Instruction.ShiftLeft, GenerateShiftLeft); + Add(Instruction.ShiftRightSI, GenerateShiftRightSI); + Add(Instruction.ShiftRightUI, GenerateShiftRightUI); + Add(Instruction.SignExtend16, GenerateSignExtend16); + Add(Instruction.SignExtend32, GenerateSignExtend32); + Add(Instruction.SignExtend8, GenerateSignExtend8); + Add(Instruction.Spill, GenerateSpill); + Add(Instruction.SpillArg, GenerateSpillArg); + Add(Instruction.StackAlloc, GenerateStackAlloc); + Add(Instruction.Store, GenerateStore); + Add(Instruction.Store16, GenerateStore16); + Add(Instruction.Store8, GenerateStore8); + Add(Instruction.Subtract, GenerateSubtract); + Add(Instruction.Tailcall, GenerateTailcall); + Add(Instruction.VectorCreateScalar, GenerateVectorCreateScalar); + Add(Instruction.VectorExtract, GenerateVectorExtract); + Add(Instruction.VectorExtract16, GenerateVectorExtract16); + Add(Instruction.VectorExtract8, GenerateVectorExtract8); + Add(Instruction.VectorInsert, GenerateVectorInsert); + Add(Instruction.VectorInsert16, GenerateVectorInsert16); + Add(Instruction.VectorInsert8, GenerateVectorInsert8); + Add(Instruction.VectorOne, GenerateVectorOne); + Add(Instruction.VectorZero, GenerateVectorZero); + Add(Instruction.VectorZeroUpper64, GenerateVectorZeroUpper64); + Add(Instruction.VectorZeroUpper96, GenerateVectorZeroUpper96); + Add(Instruction.ZeroExtend16, GenerateZeroExtend16); + Add(Instruction.ZeroExtend32, GenerateZeroExtend32); + Add(Instruction.ZeroExtend8, GenerateZeroExtend8); + + static void Add(Instruction inst, Action func) + { + _instTable[(int)inst] = func; + } + } + + public static CompiledFunction Generate(CompilerContext cctx) + { + ControlFlowGraph cfg = cctx.Cfg; + + Logger.StartPass(PassName.Optimization); + + if (cctx.Options.HasFlag(CompilerOptions.Optimize)) + { + if (cctx.Options.HasFlag(CompilerOptions.SsaForm)) + { + Optimizer.RunPass(cfg); + } + + BlockPlacement.RunPass(cfg); + } + + Arm64Optimizer.RunPass(cfg); + + Logger.EndPass(PassName.Optimization, cfg); + + Logger.StartPass(PassName.PreAllocation); + + StackAllocator stackAlloc = new(); + + PreAllocator.RunPass(cctx, stackAlloc, out int maxCallArgs); + + Logger.EndPass(PassName.PreAllocation, cfg); + + Logger.StartPass(PassName.RegisterAllocation); + + if (cctx.Options.HasFlag(CompilerOptions.SsaForm)) + { + Ssa.Deconstruct(cfg); + } + + IRegisterAllocator regAlloc; + + if (cctx.Options.HasFlag(CompilerOptions.Lsra)) + { + regAlloc = new LinearScanAllocator(); + } + else + { + regAlloc = new HybridAllocator(); + } + + RegisterMasks regMasks = new( + CallingConvention.GetIntAvailableRegisters(), + CallingConvention.GetVecAvailableRegisters(), + CallingConvention.GetIntCallerSavedRegisters(), + CallingConvention.GetVecCallerSavedRegisters(), + CallingConvention.GetIntCalleeSavedRegisters(), + CallingConvention.GetVecCalleeSavedRegisters(), + RegistersCount); + + AllocationResult allocResult = regAlloc.RunPass(cfg, stackAlloc, regMasks); + + Logger.EndPass(PassName.RegisterAllocation, cfg); + + Logger.StartPass(PassName.CodeGeneration); + + //Console.Error.WriteLine(IRDumper.GetDump(cfg)); + + bool relocatable = (cctx.Options & CompilerOptions.Relocatable) != 0; + + CodeGenContext context = new(allocResult, maxCallArgs, cfg.Blocks.Count, relocatable); + + UnwindInfo unwindInfo = WritePrologue(context); + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + context.EnterBlock(block); + + for (Operation node = block.Operations.First; node != default;) + { + node = GenerateOperation(context, node); + } + + if (block.SuccessorsCount == 0) + { + // The only blocks which can have 0 successors are exit blocks. + Operation last = block.Operations.Last; + + Debug.Assert(last.Instruction == Instruction.Tailcall || + last.Instruction == Instruction.Return); + } + else + { + BasicBlock succ = block.GetSuccessor(0); + + if (succ != block.ListNext) + { + context.JumpTo(succ); + } + } + } + + (byte[] code, RelocInfo relocInfo) = context.GetCode(); + + Logger.EndPass(PassName.CodeGeneration); + + return new CompiledFunction(code, unwindInfo, relocInfo); + } + + private static Operation GenerateOperation(CodeGenContext context, Operation operation) + { + if (operation.Instruction == Instruction.Extended) + { + CodeGeneratorIntrinsic.GenerateOperation(context, operation); + } + else + { + if (IsLoadOrStore(operation) && + operation.ListNext != default && + operation.ListNext.Instruction == operation.Instruction && + TryPairMemoryOp(context, operation, operation.ListNext)) + { + // Skip next operation if we managed to pair them. + return operation.ListNext.ListNext; + } + + Action func = _instTable[(int)operation.Instruction]; + + if (func != null) + { + func(context, operation); + } + else + { + throw new ArgumentException($"Invalid instruction \"{operation.Instruction}\"."); + } + } + + return operation.ListNext; + } + + private static void GenerateAdd(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + // ValidateBinOp(dest, src1, src2); + + if (dest.Type.IsInteger()) + { + context.Assembler.Add(dest, src1, src2); + } + else + { + context.Assembler.FaddScalar(dest, src1, src2); + } + } + + private static void GenerateBitwiseAnd(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateBinOp(dest, src1, src2); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.And(dest, src1, src2); + } + + private static void GenerateBitwiseExclusiveOr(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateBinOp(dest, src1, src2); + + if (dest.Type.IsInteger()) + { + context.Assembler.Eor(dest, src1, src2); + } + else + { + context.Assembler.EorVector(dest, src1, src2); + } + } + + private static void GenerateBitwiseNot(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + ValidateUnOp(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Mvn(dest, source); + } + + private static void GenerateBitwiseOr(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateBinOp(dest, src1, src2); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Orr(dest, src1, src2); + } + + private static void GenerateBranchIf(CodeGenContext context, Operation operation) + { + Operand comp = operation.GetSource(2); + + Debug.Assert(comp.Kind == OperandKind.Constant); + + var cond = ((Comparison)comp.AsInt32()).ToArmCondition(); + + GenerateCompareCommon(context, operation); + + context.JumpTo(cond, context.CurrBlock.GetSuccessor(1)); + } + + private static void GenerateByteSwap(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + ValidateUnOp(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Rev(dest, source); + } + + private static void GenerateCall(CodeGenContext context, Operation operation) + { + context.Assembler.Blr(operation.GetSource(0)); + } + + private static void GenerateCompare(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand comp = operation.GetSource(2); + + Debug.Assert(dest.Type == OperandType.I32); + Debug.Assert(comp.Kind == OperandKind.Constant); + + var cond = ((Comparison)comp.AsInt32()).ToArmCondition(); + + GenerateCompareCommon(context, operation); + + context.Assembler.Cset(dest, cond); + } + + private static void GenerateCompareAndSwap(CodeGenContext context, Operation operation) + { + if (operation.SourcesCount == 5) // CompareAndSwap128 has 5 sources, compared to CompareAndSwap64/32's 3. + { + Operand actualLow = operation.GetDestination(0); + Operand actualHigh = operation.GetDestination(1); + Operand temp0 = operation.GetDestination(2); + Operand temp1 = operation.GetDestination(3); + Operand address = operation.GetSource(0); + Operand expectedLow = operation.GetSource(1); + Operand expectedHigh = operation.GetSource(2); + Operand desiredLow = operation.GetSource(3); + Operand desiredHigh = operation.GetSource(4); + + GenerateAtomicDcas( + context, + address, + expectedLow, + expectedHigh, + desiredLow, + desiredHigh, + actualLow, + actualHigh, + temp0, + temp1); + } + else + { + Operand actual = operation.GetDestination(0); + Operand result = operation.GetDestination(1); + Operand address = operation.GetSource(0); + Operand expected = operation.GetSource(1); + Operand desired = operation.GetSource(2); + + GenerateAtomicCas(context, address, expected, desired, actual, result, AccessSize.Auto); + } + } + + private static void GenerateCompareAndSwap16(CodeGenContext context, Operation operation) + { + Operand actual = operation.GetDestination(0); + Operand result = operation.GetDestination(1); + Operand address = operation.GetSource(0); + Operand expected = operation.GetSource(1); + Operand desired = operation.GetSource(2); + + GenerateAtomicCas(context, address, expected, desired, actual, result, AccessSize.Hword); + } + + private static void GenerateCompareAndSwap8(CodeGenContext context, Operation operation) + { + Operand actual = operation.GetDestination(0); + Operand result = operation.GetDestination(1); + Operand address = operation.GetSource(0); + Operand expected = operation.GetSource(1); + Operand desired = operation.GetSource(2); + + GenerateAtomicCas(context, address, expected, desired, actual, result, AccessSize.Byte); + } + + private static void GenerateCompareCommon(CodeGenContext context, Operation operation) + { + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(src1, src2); + + Debug.Assert(src1.Type.IsInteger()); + + context.Assembler.Cmp(src1, src2); + } + + private static void GenerateConditionalSelect(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + EnsureSameType(dest, src2, src3); + + Debug.Assert(dest.Type.IsInteger()); + Debug.Assert(src1.Type == OperandType.I32); + + context.Assembler.Cmp (src1, Const(src1.Type, 0)); + context.Assembler.Csel(dest, src2, src3, ArmCondition.Ne); + } + + private static void GenerateConvertI64ToI32(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.I32 && source.Type == OperandType.I64); + + context.Assembler.Mov(dest, Register(source, OperandType.I32)); + } + + private static void GenerateConvertToFP(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.FP32 || dest.Type == OperandType.FP64); + Debug.Assert(dest.Type != source.Type); + Debug.Assert(source.Type != OperandType.V128); + + if (source.Type.IsInteger()) + { + context.Assembler.ScvtfScalar(dest, source); + } + else + { + context.Assembler.FcvtScalar(dest, source); + } + } + + private static void GenerateConvertToFPUI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.FP32 || dest.Type == OperandType.FP64); + Debug.Assert(dest.Type != source.Type); + Debug.Assert(source.Type.IsInteger()); + + context.Assembler.UcvtfScalar(dest, source); + } + + private static void GenerateCopy(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + EnsureSameType(dest, source); + + Debug.Assert(dest.Type.IsInteger() || source.Kind != OperandKind.Constant); + + // Moves to the same register are useless. + if (dest.Kind == source.Kind && dest.Value == source.Value) + { + return; + } + + if (dest.Kind == OperandKind.Register && source.Kind == OperandKind.Constant) + { + if (source.Relocatable) + { + context.ReserveRelocatableConstant(dest, source.Symbol, source.Value); + } + else + { + GenerateConstantCopy(context, dest, source.Value); + } + } + else + { + context.Assembler.Mov(dest, source); + } + } + + private static void GenerateCountLeadingZeros(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + EnsureSameType(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Clz(dest, source); + } + + private static void GenerateDivide(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand dividend = operation.GetSource(0); + Operand divisor = operation.GetSource(1); + + ValidateBinOp(dest, dividend, divisor); + + if (dest.Type.IsInteger()) + { + context.Assembler.Sdiv(dest, dividend, divisor); + } + else + { + context.Assembler.FdivScalar(dest, dividend, divisor); + } + } + + private static void GenerateDivideUI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand dividend = operation.GetSource(0); + Operand divisor = operation.GetSource(1); + + ValidateBinOp(dest, dividend, divisor); + + context.Assembler.Udiv(dest, dividend, divisor); + } + + private static void GenerateLoad(CodeGenContext context, Operation operation) + { + Operand value = operation.Destination; + Operand address = operation.GetSource(0); + + context.Assembler.Ldr(value, address); + } + + private static void GenerateLoad16(CodeGenContext context, Operation operation) + { + Operand value = operation.Destination; + Operand address = operation.GetSource(0); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.LdrhRiUn(value, address, 0); + } + + private static void GenerateLoad8(CodeGenContext context, Operation operation) + { + Operand value = operation.Destination; + Operand address = operation.GetSource(0); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.LdrbRiUn(value, address, 0); + } + + private static void GenerateMemoryBarrier(CodeGenContext context, Operation operation) + { + context.Assembler.Dmb(0xf); + } + + private static void GenerateMultiply(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(dest, src1, src2); + + if (dest.Type.IsInteger()) + { + context.Assembler.Mul(dest, src1, src2); + } + else + { + context.Assembler.FmulScalar(dest, src1, src2); + } + } + + private static void GenerateMultiply64HighSI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(dest, src1, src2); + + Debug.Assert(dest.Type == OperandType.I64); + + context.Assembler.Smulh(dest, src1, src2); + } + + private static void GenerateMultiply64HighUI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(dest, src1, src2); + + Debug.Assert(dest.Type == OperandType.I64); + + context.Assembler.Umulh(dest, src1, src2); + } + + private static void GenerateNegate(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + ValidateUnOp(dest, source); + + if (dest.Type.IsInteger()) + { + context.Assembler.Neg(dest, source); + } + else + { + context.Assembler.FnegScalar(dest, source); + } + } + + private static void GenerateLoad(CodeGenContext context, Operand value, Operand address, int offset) + { + if (CodeGenCommon.ConstFitsOnUImm12(offset, value.Type)) + { + context.Assembler.LdrRiUn(value, address, offset); + } + else if (CodeGenCommon.ConstFitsOnSImm9(offset)) + { + context.Assembler.Ldur(value, address, offset); + } + else + { + Operand tempAddress = Register(CodeGenCommon.ReservedRegister); + GenerateConstantCopy(context, tempAddress, (ulong)offset); + context.Assembler.Add(tempAddress, address, tempAddress, ArmExtensionType.Uxtx); // Address might be SP and must be the first input. + context.Assembler.LdrRiUn(value, tempAddress, 0); + } + } + + private static void GenerateReturn(CodeGenContext context, Operation operation) + { + WriteEpilogue(context); + + context.Assembler.Ret(Register(LrRegister)); + } + + private static void GenerateRotateRight(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Ror(dest, src1, src2); + } + + private static void GenerateShiftLeft(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Lsl(dest, src1, src2); + } + + private static void GenerateShiftRightSI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Asr(dest, src1, src2); + } + + private static void GenerateShiftRightUI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Lsr(dest, src1, src2); + } + + private static void GenerateSignExtend16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Sxth(dest, source); + } + + private static void GenerateSignExtend32(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Sxtw(dest, source); + } + + private static void GenerateSignExtend8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Sxtb(dest, source); + } + + private static void GenerateFill(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand offset = operation.GetSource(0); + + Debug.Assert(offset.Kind == OperandKind.Constant); + + int offs = offset.AsInt32() + context.CallArgsRegionSize + context.FpLrSaveRegionSize; + + GenerateLoad(context, dest, Register(SpRegister), offs); + } + + private static void GenerateStore(CodeGenContext context, Operand value, Operand address, int offset) + { + if (CodeGenCommon.ConstFitsOnUImm12(offset, value.Type)) + { + context.Assembler.StrRiUn(value, address, offset); + } + else if (CodeGenCommon.ConstFitsOnSImm9(offset)) + { + context.Assembler.Stur(value, address, offset); + } + else + { + Operand tempAddress = Register(CodeGenCommon.ReservedRegister); + GenerateConstantCopy(context, tempAddress, (ulong)offset); + context.Assembler.Add(tempAddress, address, tempAddress, ArmExtensionType.Uxtx); // Address might be SP and must be the first input. + context.Assembler.StrRiUn(value, tempAddress, 0); + } + } + + private static void GenerateSpill(CodeGenContext context, Operation operation) + { + GenerateSpill(context, operation, context.CallArgsRegionSize + context.FpLrSaveRegionSize); + } + + private static void GenerateSpillArg(CodeGenContext context, Operation operation) + { + GenerateSpill(context, operation, 0); + } + + private static void GenerateStackAlloc(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand offset = operation.GetSource(0); + + Debug.Assert(offset.Kind == OperandKind.Constant); + + int offs = offset.AsInt32() + context.CallArgsRegionSize + context.FpLrSaveRegionSize; + + context.Assembler.Add(dest, Register(SpRegister), Const(dest.Type, offs)); + } + + private static void GenerateStore(CodeGenContext context, Operation operation) + { + Operand value = operation.GetSource(1); + Operand address = operation.GetSource(0); + + context.Assembler.Str(value, address); + } + + private static void GenerateStore16(CodeGenContext context, Operation operation) + { + Operand value = operation.GetSource(1); + Operand address = operation.GetSource(0); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.StrhRiUn(value, address, 0); + } + + private static void GenerateStore8(CodeGenContext context, Operation operation) + { + Operand value = operation.GetSource(1); + Operand address = operation.GetSource(0); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.StrbRiUn(value, address, 0); + } + + private static void GenerateSpill(CodeGenContext context, Operation operation, int baseOffset) + { + Operand offset = operation.GetSource(0); + Operand source = operation.GetSource(1); + + Debug.Assert(offset.Kind == OperandKind.Constant); + + int offs = offset.AsInt32() + baseOffset; + + GenerateStore(context, source, Register(SpRegister), offs); + } + + private static void GenerateSubtract(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + // ValidateBinOp(dest, src1, src2); + + if (dest.Type.IsInteger()) + { + context.Assembler.Sub(dest, src1, src2); + } + else + { + context.Assembler.FsubScalar(dest, src1, src2); + } + } + + private static void GenerateTailcall(CodeGenContext context, Operation operation) + { + WriteEpilogue(context); + + context.Assembler.Br(operation.GetSource(0)); + } + + private static void GenerateVectorCreateScalar(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + if (dest != default) + { + Debug.Assert(!dest.Type.IsInteger() && source.Type.IsInteger()); + + OperandType destType = source.Type == OperandType.I64 ? OperandType.FP64 : OperandType.FP32; + + context.Assembler.Fmov(Register(dest, destType), source, topHalf: false); + } + } + + private static void GenerateVectorExtract(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; // Value + Operand src1 = operation.GetSource(0); // Vector + Operand src2 = operation.GetSource(1); // Index + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src2.Kind == OperandKind.Constant); + + byte index = src2.AsByte(); + + Debug.Assert(index < OperandType.V128.GetSizeInBytes() / dest.Type.GetSizeInBytes()); + + if (dest.Type.IsInteger()) + { + context.Assembler.Umov(dest, src1, index, dest.Type == OperandType.I64 ? 3 : 2); + } + else + { + context.Assembler.DupScalar(dest, src1, index, dest.Type == OperandType.FP64 ? 3 : 2); + } + } + + private static void GenerateVectorExtract16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; // Value + Operand src1 = operation.GetSource(0); // Vector + Operand src2 = operation.GetSource(1); // Index + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src2.Kind == OperandKind.Constant); + + byte index = src2.AsByte(); + + Debug.Assert(index < 8); + + context.Assembler.Umov(dest, src1, index, 1); + } + + private static void GenerateVectorExtract8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; // Value + Operand src1 = operation.GetSource(0); // Vector + Operand src2 = operation.GetSource(1); // Index + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src2.Kind == OperandKind.Constant); + + byte index = src2.AsByte(); + + Debug.Assert(index < 16); + + context.Assembler.Umov(dest, src1, index, 0); + } + + private static void GenerateVectorInsert(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); // Vector + Operand src2 = operation.GetSource(1); // Value + Operand src3 = operation.GetSource(2); // Index + + EnsureSameReg(dest, src1); + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src3.Kind == OperandKind.Constant); + + byte index = src3.AsByte(); + + if (src2.Type.IsInteger()) + { + context.Assembler.Ins(dest, src2, index, src2.Type == OperandType.I64 ? 3 : 2); + } + else + { + context.Assembler.Ins(dest, src2, 0, index, src2.Type == OperandType.FP64 ? 3 : 2); + } + } + + private static void GenerateVectorInsert16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); // Vector + Operand src2 = operation.GetSource(1); // Value + Operand src3 = operation.GetSource(2); // Index + + EnsureSameReg(dest, src1); + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src3.Kind == OperandKind.Constant); + + byte index = src3.AsByte(); + + context.Assembler.Ins(dest, src2, index, 1); + } + + private static void GenerateVectorInsert8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); // Vector + Operand src2 = operation.GetSource(1); // Value + Operand src3 = operation.GetSource(2); // Index + + EnsureSameReg(dest, src1); + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src3.Kind == OperandKind.Constant); + + byte index = src3.AsByte(); + + context.Assembler.Ins(dest, src2, index, 0); + } + + private static void GenerateVectorOne(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + + Debug.Assert(!dest.Type.IsInteger()); + + context.Assembler.CmeqVector(dest, dest, dest, 2); + } + + private static void GenerateVectorZero(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + + Debug.Assert(!dest.Type.IsInteger()); + + context.Assembler.EorVector(dest, dest, dest); + } + + private static void GenerateVectorZeroUpper64(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.V128 && source.Type == OperandType.V128); + + context.Assembler.Fmov(Register(dest, OperandType.FP64), Register(source, OperandType.FP64)); + } + + private static void GenerateVectorZeroUpper96(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.V128 && source.Type == OperandType.V128); + + context.Assembler.Fmov(Register(dest, OperandType.FP32), Register(source, OperandType.FP32)); + } + + private static void GenerateZeroExtend16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Uxth(dest, source); + } + + private static void GenerateZeroExtend32(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + // We can eliminate the move if source is already 32-bit and the registers are the same. + if (dest.Value == source.Value && source.Type == OperandType.I32) + { + return; + } + + context.Assembler.Mov(Register(dest.GetRegister().Index, OperandType.I32), source); + } + + private static void GenerateZeroExtend8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Uxtb(dest, source); + } + + private static UnwindInfo WritePrologue(CodeGenContext context) + { + List pushEntries = new List(); + + Operand rsp = Register(SpRegister); + + int intMask = CallingConvention.GetIntCalleeSavedRegisters() & context.AllocResult.IntUsedRegisters; + int vecMask = CallingConvention.GetFpCalleeSavedRegisters() & context.AllocResult.VecUsedRegisters; + + int intCalleeSavedRegsCount = BitOperations.PopCount((uint)intMask); + int vecCalleeSavedRegsCount = BitOperations.PopCount((uint)vecMask); + + int calleeSaveRegionSize = Align16(intCalleeSavedRegsCount * 8 + vecCalleeSavedRegsCount * 8); + + int offset = 0; + + WritePrologueCalleeSavesPreIndexed(context, pushEntries, ref intMask, ref offset, calleeSaveRegionSize, OperandType.I64); + WritePrologueCalleeSavesPreIndexed(context, pushEntries, ref vecMask, ref offset, calleeSaveRegionSize, OperandType.FP64); + + int localSize = Align16(context.AllocResult.SpillRegionSize + context.FpLrSaveRegionSize); + int outArgsSize = context.CallArgsRegionSize; + + if (CodeGenCommon.ConstFitsOnSImm7(localSize, DWordScale)) + { + if (context.HasCall) + { + context.Assembler.StpRiPre(Register(FpRegister), Register(LrRegister), rsp, -localSize); + context.Assembler.MovSp(Register(FpRegister), rsp); + } + + if (outArgsSize != 0) + { + context.Assembler.Sub(rsp, rsp, Const(OperandType.I64, outArgsSize)); + } + } + else + { + int frameSize = localSize + outArgsSize; + if (frameSize != 0) + { + if (CodeGenCommon.ConstFitsOnUImm12(frameSize)) + { + context.Assembler.Sub(rsp, rsp, Const(OperandType.I64, frameSize)); + } + else + { + Operand tempSize = Register(CodeGenCommon.ReservedRegister); + GenerateConstantCopy(context, tempSize, (ulong)frameSize); + context.Assembler.Sub(rsp, rsp, tempSize, ArmExtensionType.Uxtx); + } + } + + context.Assembler.StpRiUn(Register(FpRegister), Register(LrRegister), rsp, outArgsSize); + + if (outArgsSize != 0) + { + context.Assembler.Add(Register(FpRegister), Register(SpRegister), Const(OperandType.I64, outArgsSize)); + } + else + { + context.Assembler.MovSp(Register(FpRegister), Register(SpRegister)); + } + } + + return new UnwindInfo(pushEntries.ToArray(), context.StreamOffset); + } + + private static void WritePrologueCalleeSavesPreIndexed( + CodeGenContext context, + List pushEntries, + ref int mask, + ref int offset, + int calleeSaveRegionSize, + OperandType type) + { + if ((BitOperations.PopCount((uint)mask) & 1) != 0) + { + int reg = BitOperations.TrailingZeroCount(mask); + + pushEntries.Add(new UnwindPushEntry(UnwindPseudoOp.PushReg, context.StreamOffset, regIndex: reg)); + + mask &= ~(1 << reg); + + if (offset != 0) + { + context.Assembler.StrRiUn(Register(reg, type), Register(SpRegister), offset); + } + else + { + context.Assembler.StrRiPre(Register(reg, type), Register(SpRegister), -calleeSaveRegionSize); + } + + offset += type.GetSizeInBytes(); + } + + while (mask != 0) + { + int reg = BitOperations.TrailingZeroCount(mask); + + pushEntries.Add(new UnwindPushEntry(UnwindPseudoOp.PushReg, context.StreamOffset, regIndex: reg)); + + mask &= ~(1 << reg); + + int reg2 = BitOperations.TrailingZeroCount(mask); + + pushEntries.Add(new UnwindPushEntry(UnwindPseudoOp.PushReg, context.StreamOffset, regIndex: reg2)); + + mask &= ~(1 << reg2); + + if (offset != 0) + { + context.Assembler.StpRiUn(Register(reg, type), Register(reg2, type), Register(SpRegister), offset); + } + else + { + context.Assembler.StpRiPre(Register(reg, type), Register(reg2, type), Register(SpRegister), -calleeSaveRegionSize); + } + + offset += type.GetSizeInBytes() * 2; + } + } + + private static void WriteEpilogue(CodeGenContext context) + { + Operand rsp = Register(SpRegister); + + int localSize = Align16(context.AllocResult.SpillRegionSize + context.FpLrSaveRegionSize); + int outArgsSize = context.CallArgsRegionSize; + + if (CodeGenCommon.ConstFitsOnSImm7(localSize, DWordScale)) + { + if (outArgsSize != 0) + { + context.Assembler.Add(rsp, rsp, Const(OperandType.I64, outArgsSize)); + } + + if (context.HasCall) + { + context.Assembler.LdpRiPost(Register(FpRegister), Register(LrRegister), rsp, localSize); + } + } + else + { + if (context.HasCall) + { + context.Assembler.LdpRiUn(Register(FpRegister), Register(LrRegister), rsp, outArgsSize); + } + + int frameSize = localSize + outArgsSize; + if (frameSize != 0) + { + if (CodeGenCommon.ConstFitsOnUImm12(frameSize)) + { + context.Assembler.Add(rsp, rsp, Const(OperandType.I64, frameSize)); + } + else + { + Operand tempSize = Register(CodeGenCommon.ReservedRegister); + GenerateConstantCopy(context, tempSize, (ulong)frameSize); + context.Assembler.Add(rsp, rsp, tempSize, ArmExtensionType.Uxtx); + } + } + } + + int intMask = CallingConvention.GetIntCalleeSavedRegisters() & context.AllocResult.IntUsedRegisters; + int vecMask = CallingConvention.GetFpCalleeSavedRegisters() & context.AllocResult.VecUsedRegisters; + + int intCalleeSavedRegsCount = BitOperations.PopCount((uint)intMask); + int vecCalleeSavedRegsCount = BitOperations.PopCount((uint)vecMask); + + int offset = intCalleeSavedRegsCount * 8 + vecCalleeSavedRegsCount * 8; + int calleeSaveRegionSize = Align16(offset); + + WriteEpilogueCalleeSavesPostIndexed(context, ref vecMask, ref offset, calleeSaveRegionSize, OperandType.FP64); + WriteEpilogueCalleeSavesPostIndexed(context, ref intMask, ref offset, calleeSaveRegionSize, OperandType.I64); + } + + private static void WriteEpilogueCalleeSavesPostIndexed( + CodeGenContext context, + ref int mask, + ref int offset, + int calleeSaveRegionSize, + OperandType type) + { + while (mask != 0) + { + int reg = BitUtils.HighestBitSet(mask); + + mask &= ~(1 << reg); + + if (mask != 0) + { + int reg2 = BitUtils.HighestBitSet(mask); + + mask &= ~(1 << reg2); + + offset -= type.GetSizeInBytes() * 2; + + if (offset != 0) + { + context.Assembler.LdpRiUn(Register(reg2, type), Register(reg, type), Register(SpRegister), offset); + } + else + { + context.Assembler.LdpRiPost(Register(reg2, type), Register(reg, type), Register(SpRegister), calleeSaveRegionSize); + } + } + else + { + offset -= type.GetSizeInBytes(); + + if (offset != 0) + { + context.Assembler.LdrRiUn(Register(reg, type), Register(SpRegister), offset); + } + else + { + context.Assembler.LdrRiPost(Register(reg, type), Register(SpRegister), calleeSaveRegionSize); + } + } + } + } + + private static void GenerateConstantCopy(CodeGenContext context, Operand dest, ulong value) + { + if (value == 0) + { + context.Assembler.Mov(dest, Register(ZrRegister, dest.Type)); + } + else if (CodeGenCommon.TryEncodeBitMask(dest.Type, value, out _, out _, out _)) + { + context.Assembler.Orr(dest, Register(ZrRegister, dest.Type), Const(dest.Type, (long)value)); + } + else + { + int hw = 0; + bool first = true; + + while (value != 0) + { + int valueLow = (ushort)value; + if (valueLow != 0) + { + if (first) + { + context.Assembler.Movz(dest, valueLow, hw); + first = false; + } + else + { + context.Assembler.Movk(dest, valueLow, hw); + } + } + + hw++; + value >>= 16; + } + } + } + + private static void GenerateAtomicCas( + CodeGenContext context, + Operand address, + Operand expected, + Operand desired, + Operand actual, + Operand result, + AccessSize accessSize) + { + int startOffset = context.StreamOffset; + + switch (accessSize) + { + case AccessSize.Byte: + context.Assembler.Ldaxrb(actual, address); + break; + case AccessSize.Hword: + context.Assembler.Ldaxrh(actual, address); + break; + default: + context.Assembler.Ldaxr(actual, address); + break; + } + + context.Assembler.Cmp(actual, expected); + + context.JumpToNear(ArmCondition.Ne); + + switch (accessSize) + { + case AccessSize.Byte: + context.Assembler.Stlxrb(desired, address, result); + break; + case AccessSize.Hword: + context.Assembler.Stlxrh(desired, address, result); + break; + default: + context.Assembler.Stlxr(desired, address, result); + break; + } + + context.Assembler.Cbnz(result, startOffset - context.StreamOffset); // Retry if store failed. + + context.JumpHere(); + + context.Assembler.Clrex(); + } + + private static void GenerateAtomicDcas( + CodeGenContext context, + Operand address, + Operand expectedLow, + Operand expectedHigh, + Operand desiredLow, + Operand desiredHigh, + Operand actualLow, + Operand actualHigh, + Operand temp0, + Operand temp1) + { + int startOffset = context.StreamOffset; + + context.Assembler.Ldaxp(actualLow, actualHigh, address); + context.Assembler.Eor(temp0, actualHigh, expectedHigh); + context.Assembler.Eor(temp1, actualLow, expectedLow); + context.Assembler.Orr(temp0, temp1, temp0); + + context.JumpToNearIfNotZero(temp0); + + Operand result = Register(temp0, OperandType.I32); + + context.Assembler.Stlxp(desiredLow, desiredHigh, address, result); + context.Assembler.Cbnz(result, startOffset - context.StreamOffset); // Retry if store failed. + + context.JumpHere(); + + context.Assembler.Clrex(); + } + + private static bool TryPairMemoryOp(CodeGenContext context, Operation currentOp, Operation nextOp) + { + if (!TryGetMemOpBaseAndOffset(currentOp, out Operand op1Base, out int op1Offset)) + { + return false; + } + + if (!TryGetMemOpBaseAndOffset(nextOp, out Operand op2Base, out int op2Offset)) + { + return false; + } + + if (op1Base != op2Base) + { + return false; + } + + OperandType valueType = GetMemOpValueType(currentOp); + + if (valueType != GetMemOpValueType(nextOp) || op1Offset + valueType.GetSizeInBytes() != op2Offset) + { + return false; + } + + if (!CodeGenCommon.ConstFitsOnSImm7(op1Offset, valueType.GetSizeInBytesLog2())) + { + return false; + } + + if (currentOp.Instruction == Instruction.Load) + { + context.Assembler.LdpRiUn(currentOp.Destination, nextOp.Destination, op1Base, op1Offset); + } + else if (currentOp.Instruction == Instruction.Store) + { + context.Assembler.StpRiUn(currentOp.GetSource(1), nextOp.GetSource(1), op1Base, op1Offset); + } + else + { + return false; + } + + return true; + } + + private static bool IsLoadOrStore(Operation operation) + { + return operation.Instruction == Instruction.Load || operation.Instruction == Instruction.Store; + } + + private static OperandType GetMemOpValueType(Operation operation) + { + if (operation.Destination != default) + { + return operation.Destination.Type; + } + + return operation.GetSource(1).Type; + } + + private static bool TryGetMemOpBaseAndOffset(Operation operation, out Operand baseAddress, out int offset) + { + baseAddress = default; + offset = 0; + Operand address = operation.GetSource(0); + + if (address.Kind != OperandKind.Memory) + { + return false; + } + + MemoryOperand memOp = address.GetMemory(); + Operand baseOp = memOp.BaseAddress; + + if (baseOp == default) + { + baseOp = memOp.Index; + + if (baseOp == default || memOp.Scale != Multiplier.x1) + { + return false; + } + } + if (memOp.Index != default) + { + return false; + } + + baseAddress = memOp.BaseAddress; + offset = memOp.Displacement; + + return true; + } + + private static Operand Register(Operand operand, OperandType type = OperandType.I64) + { + return Register(operand.GetRegister().Index, type); + } + + private static Operand Register(int register, OperandType type = OperandType.I64) + { + return Factory.Register(register, RegisterType.Integer, type); + } + + private static int Align16(int value) + { + return (value + 0xf) & ~0xf; + } + + [Conditional("DEBUG")] + private static void ValidateUnOp(Operand dest, Operand source) + { + // Destination and source aren't forced to be equals + // EnsureSameReg (dest, source); + EnsureSameType(dest, source); + } + + [Conditional("DEBUG")] + private static void ValidateBinOp(Operand dest, Operand src1, Operand src2) + { + // Destination and source aren't forced to be equals + // EnsureSameReg (dest, src1); + EnsureSameType(dest, src1, src2); + } + + [Conditional("DEBUG")] + private static void ValidateShift(Operand dest, Operand src1, Operand src2) + { + // Destination and source aren't forced to be equals + // EnsureSameReg (dest, src1); + EnsureSameType(dest, src1); + + Debug.Assert(dest.Type.IsInteger() && src2.Type == OperandType.I32); + } + + private static void EnsureSameReg(Operand op1, Operand op2) + { + Debug.Assert(op1.Kind == OperandKind.Register || op1.Kind == OperandKind.Memory); + Debug.Assert(op1.Kind == op2.Kind); + Debug.Assert(op1.Value == op2.Value); + } + + private static void EnsureSameType(Operand op1, Operand op2) + { + Debug.Assert(op1.Type == op2.Type); + } + + private static void EnsureSameType(Operand op1, Operand op2, Operand op3) + { + Debug.Assert(op1.Type == op2.Type); + Debug.Assert(op1.Type == op3.Type); + } + + private static void EnsureSameType(Operand op1, Operand op2, Operand op3, Operand op4) + { + Debug.Assert(op1.Type == op2.Type); + Debug.Assert(op1.Type == op3.Type); + Debug.Assert(op1.Type == op4.Type); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/Arm64/CodeGeneratorIntrinsic.cs b/src/ARMeilleure/CodeGen/Arm64/CodeGeneratorIntrinsic.cs new file mode 100644 index 00000000..aaa00bb6 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/CodeGeneratorIntrinsic.cs @@ -0,0 +1,662 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Diagnostics; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static class CodeGeneratorIntrinsic + { + public static void GenerateOperation(CodeGenContext context, Operation operation) + { + Intrinsic intrin = operation.Intrinsic; + + IntrinsicInfo info = IntrinsicTable.GetInfo(intrin & ~(Intrinsic.Arm64VTypeMask | Intrinsic.Arm64VSizeMask)); + + switch (info.Type) + { + case IntrinsicType.ScalarUnary: + GenerateVectorUnary( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0)); + break; + case IntrinsicType.ScalarUnaryByElem: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorUnaryByElem( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(1).AsInt32(), + operation.Destination, + operation.GetSource(0)); + break; + case IntrinsicType.ScalarBinary: + GenerateVectorBinary( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.ScalarBinaryFPByElem: + Debug.Assert(operation.GetSource(2).Kind == OperandKind.Constant); + + GenerateVectorBinaryFPByElem( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(2).AsInt32(), + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.ScalarBinaryRd: + GenerateVectorUnary( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1)); + break; + case IntrinsicType.ScalarBinaryShl: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorBinaryShlImm( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + (uint)operation.GetSource(1).AsInt32()); + break; + case IntrinsicType.ScalarBinaryShr: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorBinaryShrImm( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + (uint)operation.GetSource(1).AsInt32()); + break; + case IntrinsicType.ScalarFPCompare: + GenerateScalarFPCompare( + context, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.ScalarFPConvFixed: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorBinaryShrImm( + context, + 0, + ((uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift) + 2u, + info.Inst, + operation.Destination, + operation.GetSource(0), + (uint)operation.GetSource(1).AsInt32()); + break; + case IntrinsicType.ScalarFPConvFixedGpr: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateScalarFPConvGpr( + context, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + (uint)operation.GetSource(1).AsInt32()); + break; + case IntrinsicType.ScalarFPConvGpr: + GenerateScalarFPConvGpr( + context, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0)); + break; + case IntrinsicType.ScalarTernary: + GenerateScalarTernary( + context, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1), + operation.GetSource(2), + operation.GetSource(0)); + break; + case IntrinsicType.ScalarTernaryFPRdByElem: + Debug.Assert(operation.GetSource(3).Kind == OperandKind.Constant); + + GenerateVectorBinaryFPByElem( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(3).AsInt32(), + operation.Destination, + operation.GetSource(1), + operation.GetSource(2)); + break; + case IntrinsicType.ScalarTernaryShlRd: + Debug.Assert(operation.GetSource(2).Kind == OperandKind.Constant); + + GenerateVectorBinaryShlImm( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1), + (uint)operation.GetSource(2).AsInt32()); + break; + case IntrinsicType.ScalarTernaryShrRd: + Debug.Assert(operation.GetSource(2).Kind == OperandKind.Constant); + + GenerateVectorBinaryShrImm( + context, + 0, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1), + (uint)operation.GetSource(2).AsInt32()); + break; + + case IntrinsicType.VectorUnary: + GenerateVectorUnary( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0)); + break; + case IntrinsicType.VectorUnaryByElem: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorUnaryByElem( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(1).AsInt32(), + operation.Destination, + operation.GetSource(0)); + break; + case IntrinsicType.VectorBinary: + GenerateVectorBinary( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.VectorBinaryBitwise: + GenerateVectorBinary( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.VectorBinaryByElem: + Debug.Assert(operation.GetSource(2).Kind == OperandKind.Constant); + + GenerateVectorBinaryByElem( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(2).AsInt32(), + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.VectorBinaryFPByElem: + Debug.Assert(operation.GetSource(2).Kind == OperandKind.Constant); + + GenerateVectorBinaryFPByElem( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(2).AsInt32(), + operation.Destination, + operation.GetSource(0), + operation.GetSource(1)); + break; + case IntrinsicType.VectorBinaryRd: + GenerateVectorUnary( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1)); + break; + case IntrinsicType.VectorBinaryShl: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorBinaryShlImm( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + (uint)operation.GetSource(1).AsInt32()); + break; + case IntrinsicType.VectorBinaryShr: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorBinaryShrImm( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(0), + (uint)operation.GetSource(1).AsInt32()); + break; + case IntrinsicType.VectorFPConvFixed: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + + GenerateVectorBinaryShrImm( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + ((uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift) + 2u, + info.Inst, + operation.Destination, + operation.GetSource(0), + (uint)operation.GetSource(1).AsInt32()); + break; + case IntrinsicType.VectorInsertByElem: + Debug.Assert(operation.GetSource(1).Kind == OperandKind.Constant); + Debug.Assert(operation.GetSource(3).Kind == OperandKind.Constant); + + GenerateVectorInsertByElem( + context, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(3).AsInt32(), + (uint)operation.GetSource(1).AsInt32(), + operation.Destination, + operation.GetSource(2)); + break; + case IntrinsicType.VectorLookupTable: + Debug.Assert((uint)(operation.SourcesCount - 2) <= 3); + + for (int i = 1; i < operation.SourcesCount - 1; i++) + { + Register currReg = operation.GetSource(i).GetRegister(); + Register prevReg = operation.GetSource(i - 1).GetRegister(); + + Debug.Assert(prevReg.Index + 1 == currReg.Index && currReg.Type == RegisterType.Vector); + } + + GenerateVectorBinary( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + info.Inst | ((uint)(operation.SourcesCount - 2) << 13), + operation.Destination, + operation.GetSource(0), + operation.GetSource(operation.SourcesCount - 1)); + break; + case IntrinsicType.VectorTernaryFPRdByElem: + Debug.Assert(operation.GetSource(3).Kind == OperandKind.Constant); + + GenerateVectorBinaryFPByElem( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(3).AsInt32(), + operation.Destination, + operation.GetSource(1), + operation.GetSource(2)); + break; + case IntrinsicType.VectorTernaryRd: + GenerateVectorBinary( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1), + operation.GetSource(2)); + break; + case IntrinsicType.VectorTernaryRdBitwise: + GenerateVectorBinary( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + info.Inst, + operation.Destination, + operation.GetSource(1), + operation.GetSource(2)); + break; + case IntrinsicType.VectorTernaryRdByElem: + Debug.Assert(operation.GetSource(3).Kind == OperandKind.Constant); + + GenerateVectorBinaryByElem( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + (uint)operation.GetSource(3).AsInt32(), + operation.Destination, + operation.GetSource(1), + operation.GetSource(2)); + break; + case IntrinsicType.VectorTernaryShlRd: + Debug.Assert(operation.GetSource(2).Kind == OperandKind.Constant); + + GenerateVectorBinaryShlImm( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1), + (uint)operation.GetSource(2).AsInt32()); + break; + case IntrinsicType.VectorTernaryShrRd: + Debug.Assert(operation.GetSource(2).Kind == OperandKind.Constant); + + GenerateVectorBinaryShrImm( + context, + (uint)(intrin & Intrinsic.Arm64VTypeMask) >> (int)Intrinsic.Arm64VTypeShift, + (uint)(intrin & Intrinsic.Arm64VSizeMask) >> (int)Intrinsic.Arm64VSizeShift, + info.Inst, + operation.Destination, + operation.GetSource(1), + (uint)operation.GetSource(2).AsInt32()); + break; + + case IntrinsicType.GetRegister: + context.Assembler.WriteInstruction(info.Inst, operation.Destination); + break; + case IntrinsicType.SetRegister: + context.Assembler.WriteInstruction(info.Inst, operation.GetSource(0)); + break; + + default: + throw new NotImplementedException(info.Type.ToString()); + } + } + + private static void GenerateScalarFPCompare( + CodeGenContext context, + uint sz, + uint instruction, + Operand dest, + Operand rn, + Operand rm) + { + instruction |= (sz << 22); + + if (rm.Kind == OperandKind.Constant && rm.Value == 0) + { + instruction |= 0b1000; + rm = rn; + } + + context.Assembler.WriteInstructionRm16NoRet(instruction, rn, rm); + context.Assembler.Mrs(dest, 1, 3, 4, 2, 0); + } + + private static void GenerateScalarFPConvGpr( + CodeGenContext context, + uint sz, + uint instruction, + Operand rd, + Operand rn) + { + instruction |= (sz << 22); + + if (rd.Type.IsInteger()) + { + context.Assembler.WriteInstructionAuto(instruction, rd, rn); + } + else + { + if (rn.Type == OperandType.I64) + { + instruction |= Assembler.SfFlag; + } + + context.Assembler.WriteInstruction(instruction, rd, rn); + } + } + + private static void GenerateScalarFPConvGpr( + CodeGenContext context, + uint sz, + uint instruction, + Operand rd, + Operand rn, + uint fBits) + { + Debug.Assert(fBits <= 64); + + instruction |= (sz << 22); + instruction |= (64 - fBits) << 10; + + if (rd.Type.IsInteger()) + { + Debug.Assert(rd.Type != OperandType.I32 || fBits <= 32); + + context.Assembler.WriteInstructionAuto(instruction, rd, rn); + } + else + { + if (rn.Type == OperandType.I64) + { + instruction |= Assembler.SfFlag; + } + else + { + Debug.Assert(fBits <= 32); + } + + context.Assembler.WriteInstruction(instruction, rd, rn); + } + + } + + private static void GenerateScalarTernary( + CodeGenContext context, + uint sz, + uint instruction, + Operand rd, + Operand rn, + Operand rm, + Operand ra) + { + instruction |= (sz << 22); + + context.Assembler.WriteInstruction(instruction, rd, rn, rm, ra); + } + + private static void GenerateVectorUnary( + CodeGenContext context, + uint q, + uint sz, + uint instruction, + Operand rd, + Operand rn) + { + instruction |= (q << 30) | (sz << 22); + + context.Assembler.WriteInstruction(instruction, rd, rn); + } + + private static void GenerateVectorUnaryByElem( + CodeGenContext context, + uint q, + uint sz, + uint instruction, + uint srcIndex, + Operand rd, + Operand rn) + { + uint imm5 = (srcIndex << ((int)sz + 1)) | (1u << (int)sz); + + instruction |= (q << 30) | (imm5 << 16); + + context.Assembler.WriteInstruction(instruction, rd, rn); + } + + private static void GenerateVectorBinary( + CodeGenContext context, + uint q, + uint instruction, + Operand rd, + Operand rn, + Operand rm) + { + instruction |= (q << 30); + + context.Assembler.WriteInstructionRm16(instruction, rd, rn, rm); + } + + private static void GenerateVectorBinary( + CodeGenContext context, + uint q, + uint sz, + uint instruction, + Operand rd, + Operand rn, + Operand rm) + { + instruction |= (q << 30) | (sz << 22); + + context.Assembler.WriteInstructionRm16(instruction, rd, rn, rm); + } + + private static void GenerateVectorBinaryByElem( + CodeGenContext context, + uint q, + uint size, + uint instruction, + uint srcIndex, + Operand rd, + Operand rn, + Operand rm) + { + instruction |= (q << 30) | (size << 22); + + if (size == 2) + { + instruction |= ((srcIndex & 1) << 21) | ((srcIndex & 2) << 10); + } + else + { + instruction |= ((srcIndex & 3) << 20) | ((srcIndex & 4) << 9); + } + + context.Assembler.WriteInstructionRm16(instruction, rd, rn, rm); + } + + private static void GenerateVectorBinaryFPByElem( + CodeGenContext context, + uint q, + uint sz, + uint instruction, + uint srcIndex, + Operand rd, + Operand rn, + Operand rm) + { + instruction |= (q << 30) | (sz << 22); + + if (sz != 0) + { + instruction |= (srcIndex & 1) << 11; + } + else + { + instruction |= ((srcIndex & 1) << 21) | ((srcIndex & 2) << 10); + } + + context.Assembler.WriteInstructionRm16(instruction, rd, rn, rm); + } + + private static void GenerateVectorBinaryShlImm( + CodeGenContext context, + uint q, + uint sz, + uint instruction, + Operand rd, + Operand rn, + uint shift) + { + instruction |= (q << 30); + + Debug.Assert(shift >= 0 && shift < (8u << (int)sz)); + + uint imm = (8u << (int)sz) | (shift & (0x3fu >> (int)(3 - sz))); + + instruction |= (imm << 16); + + context.Assembler.WriteInstruction(instruction, rd, rn); + } + + private static void GenerateVectorBinaryShrImm( + CodeGenContext context, + uint q, + uint sz, + uint instruction, + Operand rd, + Operand rn, + uint shift) + { + instruction |= (q << 30); + + Debug.Assert(shift > 0 && shift <= (8u << (int)sz)); + + uint imm = (8u << (int)sz) | ((8u << (int)sz) - shift); + + instruction |= (imm << 16); + + context.Assembler.WriteInstruction(instruction, rd, rn); + } + + private static void GenerateVectorInsertByElem( + CodeGenContext context, + uint sz, + uint instruction, + uint srcIndex, + uint dstIndex, + Operand rd, + Operand rn) + { + uint imm4 = srcIndex << (int)sz; + uint imm5 = (dstIndex << ((int)sz + 1)) | (1u << (int)sz); + + instruction |= imm4 << 11; + instruction |= imm5 << 16; + + context.Assembler.WriteInstruction(instruction, rd, rn); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/Arm64/HardwareCapabilities.cs b/src/ARMeilleure/CodeGen/Arm64/HardwareCapabilities.cs new file mode 100644 index 00000000..99ff299e --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/HardwareCapabilities.cs @@ -0,0 +1,185 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Versioning; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static partial class HardwareCapabilities + { + static HardwareCapabilities() + { + if (!ArmBase.Arm64.IsSupported) + { + return; + } + + if (OperatingSystem.IsLinux()) + { + LinuxFeatureInfoHwCap = (LinuxFeatureFlagsHwCap)getauxval(AT_HWCAP); + LinuxFeatureInfoHwCap2 = (LinuxFeatureFlagsHwCap2)getauxval(AT_HWCAP2); + } + + if (OperatingSystem.IsMacOS()) + { + for (int i = 0; i < _sysctlNames.Length; i++) + { + if (CheckSysctlName(_sysctlNames[i])) + { + MacOsFeatureInfo |= (MacOsFeatureFlags)(1 << i); + } + } + } + } + +#region Linux + + private const ulong AT_HWCAP = 16; + private const ulong AT_HWCAP2 = 26; + + [LibraryImport("libc", SetLastError = true)] + private static partial ulong getauxval(ulong type); + + [Flags] + public enum LinuxFeatureFlagsHwCap : ulong + { + Fp = 1 << 0, + Asimd = 1 << 1, + Evtstrm = 1 << 2, + Aes = 1 << 3, + Pmull = 1 << 4, + Sha1 = 1 << 5, + Sha2 = 1 << 6, + Crc32 = 1 << 7, + Atomics = 1 << 8, + FpHp = 1 << 9, + AsimdHp = 1 << 10, + CpuId = 1 << 11, + AsimdRdm = 1 << 12, + Jscvt = 1 << 13, + Fcma = 1 << 14, + Lrcpc = 1 << 15, + DcpOp = 1 << 16, + Sha3 = 1 << 17, + Sm3 = 1 << 18, + Sm4 = 1 << 19, + AsimdDp = 1 << 20, + Sha512 = 1 << 21, + Sve = 1 << 22, + AsimdFhm = 1 << 23, + Dit = 1 << 24, + Uscat = 1 << 25, + Ilrcpc = 1 << 26, + FlagM = 1 << 27, + Ssbs = 1 << 28, + Sb = 1 << 29, + Paca = 1 << 30, + Pacg = 1UL << 31 + } + + [Flags] + public enum LinuxFeatureFlagsHwCap2 : ulong + { + Dcpodp = 1 << 0, + Sve2 = 1 << 1, + SveAes = 1 << 2, + SvePmull = 1 << 3, + SveBitperm = 1 << 4, + SveSha3 = 1 << 5, + SveSm4 = 1 << 6, + FlagM2 = 1 << 7, + Frint = 1 << 8, + SveI8mm = 1 << 9, + SveF32mm = 1 << 10, + SveF64mm = 1 << 11, + SveBf16 = 1 << 12, + I8mm = 1 << 13, + Bf16 = 1 << 14, + Dgh = 1 << 15, + Rng = 1 << 16, + Bti = 1 << 17, + Mte = 1 << 18, + Ecv = 1 << 19, + Afp = 1 << 20, + Rpres = 1 << 21, + Mte3 = 1 << 22, + Sme = 1 << 23, + Sme_i16i64 = 1 << 24, + Sme_f64f64 = 1 << 25, + Sme_i8i32 = 1 << 26, + Sme_f16f32 = 1 << 27, + Sme_b16f32 = 1 << 28, + Sme_f32f32 = 1 << 29, + Sme_fa64 = 1 << 30, + Wfxt = 1UL << 31, + Ebf16 = 1UL << 32, + Sve_Ebf16 = 1UL << 33, + Cssc = 1UL << 34, + Rprfm = 1UL << 35, + Sve2p1 = 1UL << 36 + } + + public static LinuxFeatureFlagsHwCap LinuxFeatureInfoHwCap { get; } = 0; + public static LinuxFeatureFlagsHwCap2 LinuxFeatureInfoHwCap2 { get; } = 0; + +#endregion + +#region macOS + + [LibraryImport("libSystem.dylib", SetLastError = true)] + private static unsafe partial int sysctlbyname([MarshalAs(UnmanagedType.LPStr)] string name, out int oldValue, ref ulong oldSize, IntPtr newValue, ulong newValueSize); + + [SupportedOSPlatform("macos")] + private static bool CheckSysctlName(string name) + { + ulong size = sizeof(int); + if (sysctlbyname(name, out int val, ref size, IntPtr.Zero, 0) == 0 && size == sizeof(int)) + { + return val != 0; + } + return false; + } + + private static string[] _sysctlNames = new string[] + { + "hw.optional.floatingpoint", + "hw.optional.AdvSIMD", + "hw.optional.arm.FEAT_FP16", + "hw.optional.arm.FEAT_AES", + "hw.optional.arm.FEAT_PMULL", + "hw.optional.arm.FEAT_LSE", + "hw.optional.armv8_crc32", + "hw.optional.arm.FEAT_SHA1", + "hw.optional.arm.FEAT_SHA256" + }; + + [Flags] + public enum MacOsFeatureFlags + { + Fp = 1 << 0, + AdvSimd = 1 << 1, + Fp16 = 1 << 2, + Aes = 1 << 3, + Pmull = 1 << 4, + Lse = 1 << 5, + Crc32 = 1 << 6, + Sha1 = 1 << 7, + Sha256 = 1 << 8 + } + + public static MacOsFeatureFlags MacOsFeatureInfo { get; } = 0; + +#endregion + + public static bool SupportsAdvSimd => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Asimd) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.AdvSimd); + public static bool SupportsAes => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Aes) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Aes); + public static bool SupportsPmull => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Pmull) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Pmull); + public static bool SupportsLse => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Atomics) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Lse); + public static bool SupportsCrc32 => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Crc32) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Crc32); + public static bool SupportsSha1 => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Sha1) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Sha1); + public static bool SupportsSha256 => LinuxFeatureInfoHwCap.HasFlag(LinuxFeatureFlagsHwCap.Sha2) || MacOsFeatureInfo.HasFlag(MacOsFeatureFlags.Sha256); + } +} diff --git a/src/ARMeilleure/CodeGen/Arm64/IntrinsicInfo.cs b/src/ARMeilleure/CodeGen/Arm64/IntrinsicInfo.cs new file mode 100644 index 00000000..8695db90 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/IntrinsicInfo.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.CodeGen.Arm64 +{ + struct IntrinsicInfo + { + public uint Inst { get; } + public IntrinsicType Type { get; } + + public IntrinsicInfo(uint inst, IntrinsicType type) + { + Inst = inst; + Type = type; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/Arm64/IntrinsicTable.cs b/src/ARMeilleure/CodeGen/Arm64/IntrinsicTable.cs new file mode 100644 index 00000000..a309d56d --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/IntrinsicTable.cs @@ -0,0 +1,463 @@ +using ARMeilleure.Common; +using ARMeilleure.IntermediateRepresentation; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static class IntrinsicTable + { + private static IntrinsicInfo[] _intrinTable; + + static IntrinsicTable() + { + _intrinTable = new IntrinsicInfo[EnumUtils.GetCount(typeof(Intrinsic))]; + + Add(Intrinsic.Arm64AbsS, new IntrinsicInfo(0x5e20b800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64AbsV, new IntrinsicInfo(0x0e20b800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64AddhnV, new IntrinsicInfo(0x0e204000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64AddpS, new IntrinsicInfo(0x5e31b800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64AddpV, new IntrinsicInfo(0x0e20bc00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64AddvV, new IntrinsicInfo(0x0e31b800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64AddS, new IntrinsicInfo(0x5e208400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64AddV, new IntrinsicInfo(0x0e208400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64AesdV, new IntrinsicInfo(0x4e285800u, IntrinsicType.Vector128Unary)); + Add(Intrinsic.Arm64AeseV, new IntrinsicInfo(0x4e284800u, IntrinsicType.Vector128Unary)); + Add(Intrinsic.Arm64AesimcV, new IntrinsicInfo(0x4e287800u, IntrinsicType.Vector128Unary)); + Add(Intrinsic.Arm64AesmcV, new IntrinsicInfo(0x4e286800u, IntrinsicType.Vector128Unary)); + Add(Intrinsic.Arm64AndV, new IntrinsicInfo(0x0e201c00u, IntrinsicType.VectorBinaryBitwise)); + Add(Intrinsic.Arm64BicVi, new IntrinsicInfo(0x2f001400u, IntrinsicType.VectorBinaryBitwiseImm)); + Add(Intrinsic.Arm64BicV, new IntrinsicInfo(0x0e601c00u, IntrinsicType.VectorBinaryBitwise)); + Add(Intrinsic.Arm64BifV, new IntrinsicInfo(0x2ee01c00u, IntrinsicType.VectorTernaryRdBitwise)); + Add(Intrinsic.Arm64BitV, new IntrinsicInfo(0x2ea01c00u, IntrinsicType.VectorTernaryRdBitwise)); + Add(Intrinsic.Arm64BslV, new IntrinsicInfo(0x2e601c00u, IntrinsicType.VectorTernaryRdBitwise)); + Add(Intrinsic.Arm64ClsV, new IntrinsicInfo(0x0e204800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64ClzV, new IntrinsicInfo(0x2e204800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64CmeqS, new IntrinsicInfo(0x7e208c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64CmeqV, new IntrinsicInfo(0x2e208c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64CmeqSz, new IntrinsicInfo(0x5e209800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64CmeqVz, new IntrinsicInfo(0x0e209800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64CmgeS, new IntrinsicInfo(0x5e203c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64CmgeV, new IntrinsicInfo(0x0e203c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64CmgeSz, new IntrinsicInfo(0x7e208800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64CmgeVz, new IntrinsicInfo(0x2e208800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64CmgtS, new IntrinsicInfo(0x5e203400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64CmgtV, new IntrinsicInfo(0x0e203400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64CmgtSz, new IntrinsicInfo(0x5e208800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64CmgtVz, new IntrinsicInfo(0x0e208800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64CmhiS, new IntrinsicInfo(0x7e203400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64CmhiV, new IntrinsicInfo(0x2e203400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64CmhsS, new IntrinsicInfo(0x7e203c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64CmhsV, new IntrinsicInfo(0x2e203c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64CmleSz, new IntrinsicInfo(0x7e209800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64CmleVz, new IntrinsicInfo(0x2e209800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64CmltSz, new IntrinsicInfo(0x5e20a800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64CmltVz, new IntrinsicInfo(0x0e20a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64CmtstS, new IntrinsicInfo(0x5e208c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64CmtstV, new IntrinsicInfo(0x0e208c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64CntV, new IntrinsicInfo(0x0e205800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64DupSe, new IntrinsicInfo(0x5e000400u, IntrinsicType.ScalarUnaryByElem)); + Add(Intrinsic.Arm64DupVe, new IntrinsicInfo(0x0e000400u, IntrinsicType.VectorUnaryByElem)); + Add(Intrinsic.Arm64DupGp, new IntrinsicInfo(0x0e000c00u, IntrinsicType.VectorUnaryByElem)); + Add(Intrinsic.Arm64EorV, new IntrinsicInfo(0x2e201c00u, IntrinsicType.VectorBinaryBitwise)); + Add(Intrinsic.Arm64ExtV, new IntrinsicInfo(0x2e000000u, IntrinsicType.VectorExt)); + Add(Intrinsic.Arm64FabdS, new IntrinsicInfo(0x7ea0d400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FabdV, new IntrinsicInfo(0x2ea0d400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FabsV, new IntrinsicInfo(0x0ea0f800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FabsS, new IntrinsicInfo(0x1e20c000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FacgeS, new IntrinsicInfo(0x7e20ec00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FacgeV, new IntrinsicInfo(0x2e20ec00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FacgtS, new IntrinsicInfo(0x7ea0ec00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FacgtV, new IntrinsicInfo(0x2ea0ec00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FaddpS, new IntrinsicInfo(0x7e30d800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FaddpV, new IntrinsicInfo(0x2e20d400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FaddV, new IntrinsicInfo(0x0e20d400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FaddS, new IntrinsicInfo(0x1e202800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FccmpeS, new IntrinsicInfo(0x1e200410u, IntrinsicType.ScalarFPCompareCond)); + Add(Intrinsic.Arm64FccmpS, new IntrinsicInfo(0x1e200400u, IntrinsicType.ScalarFPCompareCond)); + Add(Intrinsic.Arm64FcmeqS, new IntrinsicInfo(0x5e20e400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FcmeqV, new IntrinsicInfo(0x0e20e400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FcmeqSz, new IntrinsicInfo(0x5ea0d800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcmeqVz, new IntrinsicInfo(0x0ea0d800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcmgeS, new IntrinsicInfo(0x7e20e400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FcmgeV, new IntrinsicInfo(0x2e20e400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FcmgeSz, new IntrinsicInfo(0x7ea0c800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcmgeVz, new IntrinsicInfo(0x2ea0c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcmgtS, new IntrinsicInfo(0x7ea0e400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FcmgtV, new IntrinsicInfo(0x2ea0e400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FcmgtSz, new IntrinsicInfo(0x5ea0c800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcmgtVz, new IntrinsicInfo(0x0ea0c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcmleSz, new IntrinsicInfo(0x7ea0d800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcmleVz, new IntrinsicInfo(0x2ea0d800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcmltSz, new IntrinsicInfo(0x5ea0e800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcmltVz, new IntrinsicInfo(0x0ea0e800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcmpeS, new IntrinsicInfo(0x1e202010u, IntrinsicType.ScalarFPCompare)); + Add(Intrinsic.Arm64FcmpS, new IntrinsicInfo(0x1e202000u, IntrinsicType.ScalarFPCompare)); + Add(Intrinsic.Arm64FcselS, new IntrinsicInfo(0x1e200c00u, IntrinsicType.ScalarFcsel)); + Add(Intrinsic.Arm64FcvtasS, new IntrinsicInfo(0x5e21c800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtasV, new IntrinsicInfo(0x0e21c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtasGp, new IntrinsicInfo(0x1e240000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtauS, new IntrinsicInfo(0x7e21c800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtauV, new IntrinsicInfo(0x2e21c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtauGp, new IntrinsicInfo(0x1e250000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtlV, new IntrinsicInfo(0x0e217800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtmsS, new IntrinsicInfo(0x5e21b800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtmsV, new IntrinsicInfo(0x0e21b800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtmsGp, new IntrinsicInfo(0x1e300000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtmuS, new IntrinsicInfo(0x7e21b800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtmuV, new IntrinsicInfo(0x2e21b800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtmuGp, new IntrinsicInfo(0x1e310000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtnsS, new IntrinsicInfo(0x5e21a800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtnsV, new IntrinsicInfo(0x0e21a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtnsGp, new IntrinsicInfo(0x1e200000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtnuS, new IntrinsicInfo(0x7e21a800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtnuV, new IntrinsicInfo(0x2e21a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtnuGp, new IntrinsicInfo(0x1e210000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtnV, new IntrinsicInfo(0x0e216800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64FcvtpsS, new IntrinsicInfo(0x5ea1a800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtpsV, new IntrinsicInfo(0x0ea1a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtpsGp, new IntrinsicInfo(0x1e280000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtpuS, new IntrinsicInfo(0x7ea1a800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtpuV, new IntrinsicInfo(0x2ea1a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtpuGp, new IntrinsicInfo(0x1e290000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtxnS, new IntrinsicInfo(0x7e216800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtxnV, new IntrinsicInfo(0x2e216800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtzsSFixed, new IntrinsicInfo(0x5f00fc00u, IntrinsicType.ScalarFPConvFixed)); + Add(Intrinsic.Arm64FcvtzsVFixed, new IntrinsicInfo(0x0f00fc00u, IntrinsicType.VectorFPConvFixed)); + Add(Intrinsic.Arm64FcvtzsS, new IntrinsicInfo(0x5ea1b800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtzsV, new IntrinsicInfo(0x0ea1b800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtzsGpFixed, new IntrinsicInfo(0x1e180000u, IntrinsicType.ScalarFPConvFixedGpr)); + Add(Intrinsic.Arm64FcvtzsGp, new IntrinsicInfo(0x1e380000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtzuSFixed, new IntrinsicInfo(0x7f00fc00u, IntrinsicType.ScalarFPConvFixed)); + Add(Intrinsic.Arm64FcvtzuVFixed, new IntrinsicInfo(0x2f00fc00u, IntrinsicType.VectorFPConvFixed)); + Add(Intrinsic.Arm64FcvtzuS, new IntrinsicInfo(0x7ea1b800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FcvtzuV, new IntrinsicInfo(0x2ea1b800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FcvtzuGpFixed, new IntrinsicInfo(0x1e190000u, IntrinsicType.ScalarFPConvFixedGpr)); + Add(Intrinsic.Arm64FcvtzuGp, new IntrinsicInfo(0x1e390000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FcvtS, new IntrinsicInfo(0x1e224000u, IntrinsicType.ScalarFPConv)); + Add(Intrinsic.Arm64FdivV, new IntrinsicInfo(0x2e20fc00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FdivS, new IntrinsicInfo(0x1e201800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FmaddS, new IntrinsicInfo(0x1f000000u, IntrinsicType.ScalarTernary)); + Add(Intrinsic.Arm64FmaxnmpS, new IntrinsicInfo(0x7e30c800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FmaxnmpV, new IntrinsicInfo(0x2e20c400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FmaxnmvV, new IntrinsicInfo(0x2e30c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FmaxnmV, new IntrinsicInfo(0x0e20c400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FmaxnmS, new IntrinsicInfo(0x1e206800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FmaxpS, new IntrinsicInfo(0x7e30f800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FmaxpV, new IntrinsicInfo(0x2e20f400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FmaxvV, new IntrinsicInfo(0x2e30f800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FmaxV, new IntrinsicInfo(0x0e20f400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FmaxS, new IntrinsicInfo(0x1e204800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FminnmpS, new IntrinsicInfo(0x7eb0c800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FminnmpV, new IntrinsicInfo(0x2ea0c400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FminnmvV, new IntrinsicInfo(0x2eb0c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FminnmV, new IntrinsicInfo(0x0ea0c400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FminnmS, new IntrinsicInfo(0x1e207800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FminpS, new IntrinsicInfo(0x7eb0f800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FminpV, new IntrinsicInfo(0x2ea0f400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FminvV, new IntrinsicInfo(0x2eb0f800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FminV, new IntrinsicInfo(0x0ea0f400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FminS, new IntrinsicInfo(0x1e205800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FmlaSe, new IntrinsicInfo(0x5f801000u, IntrinsicType.ScalarTernaryFPRdByElem)); + Add(Intrinsic.Arm64FmlaVe, new IntrinsicInfo(0x0f801000u, IntrinsicType.VectorTernaryFPRdByElem)); + Add(Intrinsic.Arm64FmlaV, new IntrinsicInfo(0x0e20cc00u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64FmlsSe, new IntrinsicInfo(0x5f805000u, IntrinsicType.ScalarTernaryFPRdByElem)); + Add(Intrinsic.Arm64FmlsVe, new IntrinsicInfo(0x0f805000u, IntrinsicType.VectorTernaryFPRdByElem)); + Add(Intrinsic.Arm64FmlsV, new IntrinsicInfo(0x0ea0cc00u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64FmovVi, new IntrinsicInfo(0x0f00f400u, IntrinsicType.VectorFmovi)); + Add(Intrinsic.Arm64FmovS, new IntrinsicInfo(0x1e204000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FmovGp, new IntrinsicInfo(0x1e260000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64FmovSi, new IntrinsicInfo(0x1e201000u, IntrinsicType.ScalarFmovi)); + Add(Intrinsic.Arm64FmsubS, new IntrinsicInfo(0x1f008000u, IntrinsicType.ScalarTernary)); + Add(Intrinsic.Arm64FmulxSe, new IntrinsicInfo(0x7f809000u, IntrinsicType.ScalarBinaryFPByElem)); + Add(Intrinsic.Arm64FmulxVe, new IntrinsicInfo(0x2f809000u, IntrinsicType.VectorBinaryFPByElem)); + Add(Intrinsic.Arm64FmulxS, new IntrinsicInfo(0x5e20dc00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FmulxV, new IntrinsicInfo(0x0e20dc00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FmulSe, new IntrinsicInfo(0x5f809000u, IntrinsicType.ScalarBinaryFPByElem)); + Add(Intrinsic.Arm64FmulVe, new IntrinsicInfo(0x0f809000u, IntrinsicType.VectorBinaryFPByElem)); + Add(Intrinsic.Arm64FmulV, new IntrinsicInfo(0x2e20dc00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FmulS, new IntrinsicInfo(0x1e200800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FnegV, new IntrinsicInfo(0x2ea0f800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FnegS, new IntrinsicInfo(0x1e214000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FnmaddS, new IntrinsicInfo(0x1f200000u, IntrinsicType.ScalarTernary)); + Add(Intrinsic.Arm64FnmsubS, new IntrinsicInfo(0x1f208000u, IntrinsicType.ScalarTernary)); + Add(Intrinsic.Arm64FnmulS, new IntrinsicInfo(0x1e208800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FrecpeS, new IntrinsicInfo(0x5ea1d800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrecpeV, new IntrinsicInfo(0x0ea1d800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrecpsS, new IntrinsicInfo(0x5e20fc00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FrecpsV, new IntrinsicInfo(0x0e20fc00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FrecpxS, new IntrinsicInfo(0x5ea1f800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrintaV, new IntrinsicInfo(0x2e218800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrintaS, new IntrinsicInfo(0x1e264000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrintiV, new IntrinsicInfo(0x2ea19800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrintiS, new IntrinsicInfo(0x1e27c000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrintmV, new IntrinsicInfo(0x0e219800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrintmS, new IntrinsicInfo(0x1e254000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrintnV, new IntrinsicInfo(0x0e218800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrintnS, new IntrinsicInfo(0x1e244000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrintpV, new IntrinsicInfo(0x0ea18800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrintpS, new IntrinsicInfo(0x1e24c000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrintxV, new IntrinsicInfo(0x2e219800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrintxS, new IntrinsicInfo(0x1e274000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrintzV, new IntrinsicInfo(0x0ea19800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrintzS, new IntrinsicInfo(0x1e25c000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrsqrteS, new IntrinsicInfo(0x7ea1d800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FrsqrteV, new IntrinsicInfo(0x2ea1d800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FrsqrtsS, new IntrinsicInfo(0x5ea0fc00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64FrsqrtsV, new IntrinsicInfo(0x0ea0fc00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FsqrtV, new IntrinsicInfo(0x2ea1f800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64FsqrtS, new IntrinsicInfo(0x1e21c000u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64FsubV, new IntrinsicInfo(0x0ea0d400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64FsubS, new IntrinsicInfo(0x1e203800u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64InsVe, new IntrinsicInfo(0x6e000400u, IntrinsicType.VectorInsertByElem)); + Add(Intrinsic.Arm64InsGp, new IntrinsicInfo(0x4e001c00u, IntrinsicType.ScalarUnaryByElem)); + Add(Intrinsic.Arm64Ld1rV, new IntrinsicInfo(0x0d40c000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld1Vms, new IntrinsicInfo(0x0c402000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld1Vss, new IntrinsicInfo(0x0d400000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64Ld2rV, new IntrinsicInfo(0x0d60c000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld2Vms, new IntrinsicInfo(0x0c408000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld2Vss, new IntrinsicInfo(0x0d600000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64Ld3rV, new IntrinsicInfo(0x0d40e000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld3Vms, new IntrinsicInfo(0x0c404000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld3Vss, new IntrinsicInfo(0x0d402000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64Ld4rV, new IntrinsicInfo(0x0d60e000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld4Vms, new IntrinsicInfo(0x0c400000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64Ld4Vss, new IntrinsicInfo(0x0d602000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64MlaVe, new IntrinsicInfo(0x2f000000u, IntrinsicType.VectorTernaryRdByElem)); + Add(Intrinsic.Arm64MlaV, new IntrinsicInfo(0x0e209400u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64MlsVe, new IntrinsicInfo(0x2f004000u, IntrinsicType.VectorTernaryRdByElem)); + Add(Intrinsic.Arm64MlsV, new IntrinsicInfo(0x2e209400u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64MoviV, new IntrinsicInfo(0x0f000400u, IntrinsicType.VectorMovi)); + Add(Intrinsic.Arm64MrsFpcr, new IntrinsicInfo(0xd53b4400u, IntrinsicType.GetRegister)); + Add(Intrinsic.Arm64MsrFpcr, new IntrinsicInfo(0xd51b4400u, IntrinsicType.SetRegister)); + Add(Intrinsic.Arm64MrsFpsr, new IntrinsicInfo(0xd53b4420u, IntrinsicType.GetRegister)); + Add(Intrinsic.Arm64MsrFpsr, new IntrinsicInfo(0xd51b4420u, IntrinsicType.SetRegister)); + Add(Intrinsic.Arm64MulVe, new IntrinsicInfo(0x0f008000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64MulV, new IntrinsicInfo(0x0e209c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64MvniV, new IntrinsicInfo(0x2f000400u, IntrinsicType.VectorMvni)); + Add(Intrinsic.Arm64NegS, new IntrinsicInfo(0x7e20b800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64NegV, new IntrinsicInfo(0x2e20b800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64NotV, new IntrinsicInfo(0x2e205800u, IntrinsicType.VectorUnaryBitwise)); + Add(Intrinsic.Arm64OrnV, new IntrinsicInfo(0x0ee01c00u, IntrinsicType.VectorBinaryBitwise)); + Add(Intrinsic.Arm64OrrVi, new IntrinsicInfo(0x0f001400u, IntrinsicType.VectorBinaryBitwiseImm)); + Add(Intrinsic.Arm64OrrV, new IntrinsicInfo(0x0ea01c00u, IntrinsicType.VectorBinaryBitwise)); + Add(Intrinsic.Arm64PmullV, new IntrinsicInfo(0x0e20e000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64PmulV, new IntrinsicInfo(0x2e209c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64RaddhnV, new IntrinsicInfo(0x2e204000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64RbitV, new IntrinsicInfo(0x2e605800u, IntrinsicType.VectorUnaryBitwise)); + Add(Intrinsic.Arm64Rev16V, new IntrinsicInfo(0x0e201800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64Rev32V, new IntrinsicInfo(0x2e200800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64Rev64V, new IntrinsicInfo(0x0e200800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64RshrnV, new IntrinsicInfo(0x0f008c00u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64RsubhnV, new IntrinsicInfo(0x2e206000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64SabalV, new IntrinsicInfo(0x0e205000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64SabaV, new IntrinsicInfo(0x0e207c00u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64SabdlV, new IntrinsicInfo(0x0e207000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SabdV, new IntrinsicInfo(0x0e207400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SadalpV, new IntrinsicInfo(0x0e206800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64SaddlpV, new IntrinsicInfo(0x0e202800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64SaddlvV, new IntrinsicInfo(0x0e303800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64SaddlV, new IntrinsicInfo(0x0e200000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SaddwV, new IntrinsicInfo(0x0e201000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64ScvtfSFixed, new IntrinsicInfo(0x5f00e400u, IntrinsicType.ScalarFPConvFixed)); + Add(Intrinsic.Arm64ScvtfVFixed, new IntrinsicInfo(0x0f00e400u, IntrinsicType.VectorFPConvFixed)); + Add(Intrinsic.Arm64ScvtfS, new IntrinsicInfo(0x5e21d800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64ScvtfV, new IntrinsicInfo(0x0e21d800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64ScvtfGpFixed, new IntrinsicInfo(0x1e020000u, IntrinsicType.ScalarFPConvFixedGpr)); + Add(Intrinsic.Arm64ScvtfGp, new IntrinsicInfo(0x1e220000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64Sha1cV, new IntrinsicInfo(0x5e000000u, IntrinsicType.Vector128Binary)); + Add(Intrinsic.Arm64Sha1hV, new IntrinsicInfo(0x5e280800u, IntrinsicType.Vector128Unary)); + Add(Intrinsic.Arm64Sha1mV, new IntrinsicInfo(0x5e002000u, IntrinsicType.Vector128Binary)); + Add(Intrinsic.Arm64Sha1pV, new IntrinsicInfo(0x5e001000u, IntrinsicType.Vector128Binary)); + Add(Intrinsic.Arm64Sha1su0V, new IntrinsicInfo(0x5e003000u, IntrinsicType.Vector128Binary)); + Add(Intrinsic.Arm64Sha1su1V, new IntrinsicInfo(0x5e281800u, IntrinsicType.Vector128Unary)); + Add(Intrinsic.Arm64Sha256h2V, new IntrinsicInfo(0x5e005000u, IntrinsicType.Vector128Binary)); + Add(Intrinsic.Arm64Sha256hV, new IntrinsicInfo(0x5e004000u, IntrinsicType.Vector128Binary)); + Add(Intrinsic.Arm64Sha256su0V, new IntrinsicInfo(0x5e282800u, IntrinsicType.Vector128Unary)); + Add(Intrinsic.Arm64Sha256su1V, new IntrinsicInfo(0x5e006000u, IntrinsicType.Vector128Binary)); + Add(Intrinsic.Arm64ShaddV, new IntrinsicInfo(0x0e200400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64ShllV, new IntrinsicInfo(0x2e213800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64ShlS, new IntrinsicInfo(0x5f005400u, IntrinsicType.ScalarBinaryShl)); + Add(Intrinsic.Arm64ShlV, new IntrinsicInfo(0x0f005400u, IntrinsicType.VectorBinaryShl)); + Add(Intrinsic.Arm64ShrnV, new IntrinsicInfo(0x0f008400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64ShsubV, new IntrinsicInfo(0x0e202400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SliS, new IntrinsicInfo(0x7f005400u, IntrinsicType.ScalarTernaryShlRd)); + Add(Intrinsic.Arm64SliV, new IntrinsicInfo(0x2f005400u, IntrinsicType.VectorTernaryShlRd)); + Add(Intrinsic.Arm64SmaxpV, new IntrinsicInfo(0x0e20a400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SmaxvV, new IntrinsicInfo(0x0e30a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64SmaxV, new IntrinsicInfo(0x0e206400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SminpV, new IntrinsicInfo(0x0e20ac00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SminvV, new IntrinsicInfo(0x0e31a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64SminV, new IntrinsicInfo(0x0e206c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SmlalVe, new IntrinsicInfo(0x0f002000u, IntrinsicType.VectorTernaryRdByElem)); + Add(Intrinsic.Arm64SmlalV, new IntrinsicInfo(0x0e208000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64SmlslVe, new IntrinsicInfo(0x0f006000u, IntrinsicType.VectorTernaryRdByElem)); + Add(Intrinsic.Arm64SmlslV, new IntrinsicInfo(0x0e20a000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64SmovV, new IntrinsicInfo(0x0e002c00u, IntrinsicType.VectorUnaryByElem)); + Add(Intrinsic.Arm64SmullVe, new IntrinsicInfo(0x0f00a000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64SmullV, new IntrinsicInfo(0x0e20c000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqabsS, new IntrinsicInfo(0x5e207800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64SqabsV, new IntrinsicInfo(0x0e207800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64SqaddS, new IntrinsicInfo(0x5e200c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqaddV, new IntrinsicInfo(0x0e200c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqdmlalSe, new IntrinsicInfo(0x5f003000u, IntrinsicType.ScalarBinaryByElem)); + Add(Intrinsic.Arm64SqdmlalVe, new IntrinsicInfo(0x0f003000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64SqdmlalS, new IntrinsicInfo(0x5e209000u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqdmlalV, new IntrinsicInfo(0x0e209000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqdmlslSe, new IntrinsicInfo(0x5f007000u, IntrinsicType.ScalarBinaryByElem)); + Add(Intrinsic.Arm64SqdmlslVe, new IntrinsicInfo(0x0f007000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64SqdmlslS, new IntrinsicInfo(0x5e20b000u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqdmlslV, new IntrinsicInfo(0x0e20b000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqdmulhSe, new IntrinsicInfo(0x5f00c000u, IntrinsicType.ScalarBinaryByElem)); + Add(Intrinsic.Arm64SqdmulhVe, new IntrinsicInfo(0x0f00c000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64SqdmulhS, new IntrinsicInfo(0x5e20b400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqdmulhV, new IntrinsicInfo(0x0e20b400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqdmullSe, new IntrinsicInfo(0x5f00b000u, IntrinsicType.ScalarBinaryByElem)); + Add(Intrinsic.Arm64SqdmullVe, new IntrinsicInfo(0x0f00b000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64SqdmullS, new IntrinsicInfo(0x5e20d000u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqdmullV, new IntrinsicInfo(0x0e20d000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqnegS, new IntrinsicInfo(0x7e207800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64SqnegV, new IntrinsicInfo(0x2e207800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64SqrdmulhSe, new IntrinsicInfo(0x5f00d000u, IntrinsicType.ScalarBinaryByElem)); + Add(Intrinsic.Arm64SqrdmulhVe, new IntrinsicInfo(0x0f00d000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64SqrdmulhS, new IntrinsicInfo(0x7e20b400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqrdmulhV, new IntrinsicInfo(0x2e20b400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqrshlS, new IntrinsicInfo(0x5e205c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqrshlV, new IntrinsicInfo(0x0e205c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqrshrnS, new IntrinsicInfo(0x5f009c00u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64SqrshrnV, new IntrinsicInfo(0x0f009c00u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64SqrshrunS, new IntrinsicInfo(0x7f008c00u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64SqrshrunV, new IntrinsicInfo(0x2f008c00u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64SqshluS, new IntrinsicInfo(0x7f006400u, IntrinsicType.ScalarBinaryShl)); + Add(Intrinsic.Arm64SqshluV, new IntrinsicInfo(0x2f006400u, IntrinsicType.VectorBinaryShl)); + Add(Intrinsic.Arm64SqshlSi, new IntrinsicInfo(0x5f007400u, IntrinsicType.ScalarBinaryShl)); + Add(Intrinsic.Arm64SqshlVi, new IntrinsicInfo(0x0f007400u, IntrinsicType.VectorBinaryShl)); + Add(Intrinsic.Arm64SqshlS, new IntrinsicInfo(0x5e204c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqshlV, new IntrinsicInfo(0x0e204c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqshrnS, new IntrinsicInfo(0x5f009400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64SqshrnV, new IntrinsicInfo(0x0f009400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64SqshrunS, new IntrinsicInfo(0x7f008400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64SqshrunV, new IntrinsicInfo(0x2f008400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64SqsubS, new IntrinsicInfo(0x5e202c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SqsubV, new IntrinsicInfo(0x0e202c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SqxtnS, new IntrinsicInfo(0x5e214800u, IntrinsicType.ScalarBinaryRd)); + Add(Intrinsic.Arm64SqxtnV, new IntrinsicInfo(0x0e214800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64SqxtunS, new IntrinsicInfo(0x7e212800u, IntrinsicType.ScalarBinaryRd)); + Add(Intrinsic.Arm64SqxtunV, new IntrinsicInfo(0x2e212800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64SrhaddV, new IntrinsicInfo(0x0e201400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SriS, new IntrinsicInfo(0x7f004400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64SriV, new IntrinsicInfo(0x2f004400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64SrshlS, new IntrinsicInfo(0x5e205400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SrshlV, new IntrinsicInfo(0x0e205400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SrshrS, new IntrinsicInfo(0x5f002400u, IntrinsicType.ScalarBinaryShr)); + Add(Intrinsic.Arm64SrshrV, new IntrinsicInfo(0x0f002400u, IntrinsicType.VectorBinaryShr)); + Add(Intrinsic.Arm64SrsraS, new IntrinsicInfo(0x5f003400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64SrsraV, new IntrinsicInfo(0x0f003400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64SshllV, new IntrinsicInfo(0x0f00a400u, IntrinsicType.VectorBinaryShl)); + Add(Intrinsic.Arm64SshlS, new IntrinsicInfo(0x5e204400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SshlV, new IntrinsicInfo(0x0e204400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SshrS, new IntrinsicInfo(0x5f000400u, IntrinsicType.ScalarBinaryShr)); + Add(Intrinsic.Arm64SshrV, new IntrinsicInfo(0x0f000400u, IntrinsicType.VectorBinaryShr)); + Add(Intrinsic.Arm64SsraS, new IntrinsicInfo(0x5f001400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64SsraV, new IntrinsicInfo(0x0f001400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64SsublV, new IntrinsicInfo(0x0e202000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SsubwV, new IntrinsicInfo(0x0e203000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64St1Vms, new IntrinsicInfo(0x0c002000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64St1Vss, new IntrinsicInfo(0x0d000000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64St2Vms, new IntrinsicInfo(0x0c008000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64St2Vss, new IntrinsicInfo(0x0d200000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64St3Vms, new IntrinsicInfo(0x0c004000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64St3Vss, new IntrinsicInfo(0x0d002000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64St4Vms, new IntrinsicInfo(0x0c000000u, IntrinsicType.VectorLdSt)); + Add(Intrinsic.Arm64St4Vss, new IntrinsicInfo(0x0d202000u, IntrinsicType.VectorLdStSs)); + Add(Intrinsic.Arm64SubhnV, new IntrinsicInfo(0x0e206000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64SubS, new IntrinsicInfo(0x7e208400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64SubV, new IntrinsicInfo(0x2e208400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64SuqaddS, new IntrinsicInfo(0x5e203800u, IntrinsicType.ScalarBinaryRd)); + Add(Intrinsic.Arm64SuqaddV, new IntrinsicInfo(0x0e203800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64TblV, new IntrinsicInfo(0x0e000000u, IntrinsicType.VectorLookupTable)); + Add(Intrinsic.Arm64TbxV, new IntrinsicInfo(0x0e001000u, IntrinsicType.VectorLookupTable)); + Add(Intrinsic.Arm64Trn1V, new IntrinsicInfo(0x0e002800u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64Trn2V, new IntrinsicInfo(0x0e006800u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UabalV, new IntrinsicInfo(0x2e205000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64UabaV, new IntrinsicInfo(0x2e207c00u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64UabdlV, new IntrinsicInfo(0x2e207000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UabdV, new IntrinsicInfo(0x2e207400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UadalpV, new IntrinsicInfo(0x2e206800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64UaddlpV, new IntrinsicInfo(0x2e202800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64UaddlvV, new IntrinsicInfo(0x2e303800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64UaddlV, new IntrinsicInfo(0x2e200000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UaddwV, new IntrinsicInfo(0x2e201000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UcvtfSFixed, new IntrinsicInfo(0x7f00e400u, IntrinsicType.ScalarFPConvFixed)); + Add(Intrinsic.Arm64UcvtfVFixed, new IntrinsicInfo(0x2f00e400u, IntrinsicType.VectorFPConvFixed)); + Add(Intrinsic.Arm64UcvtfS, new IntrinsicInfo(0x7e21d800u, IntrinsicType.ScalarUnary)); + Add(Intrinsic.Arm64UcvtfV, new IntrinsicInfo(0x2e21d800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64UcvtfGpFixed, new IntrinsicInfo(0x1e030000u, IntrinsicType.ScalarFPConvFixedGpr)); + Add(Intrinsic.Arm64UcvtfGp, new IntrinsicInfo(0x1e230000u, IntrinsicType.ScalarFPConvGpr)); + Add(Intrinsic.Arm64UhaddV, new IntrinsicInfo(0x2e200400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UhsubV, new IntrinsicInfo(0x2e202400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UmaxpV, new IntrinsicInfo(0x2e20a400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UmaxvV, new IntrinsicInfo(0x2e30a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64UmaxV, new IntrinsicInfo(0x2e206400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UminpV, new IntrinsicInfo(0x2e20ac00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UminvV, new IntrinsicInfo(0x2e31a800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64UminV, new IntrinsicInfo(0x2e206c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UmlalVe, new IntrinsicInfo(0x2f002000u, IntrinsicType.VectorTernaryRdByElem)); + Add(Intrinsic.Arm64UmlalV, new IntrinsicInfo(0x2e208000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64UmlslVe, new IntrinsicInfo(0x2f006000u, IntrinsicType.VectorTernaryRdByElem)); + Add(Intrinsic.Arm64UmlslV, new IntrinsicInfo(0x2e20a000u, IntrinsicType.VectorTernaryRd)); + Add(Intrinsic.Arm64UmovV, new IntrinsicInfo(0x0e003c00u, IntrinsicType.VectorUnaryByElem)); + Add(Intrinsic.Arm64UmullVe, new IntrinsicInfo(0x2f00a000u, IntrinsicType.VectorBinaryByElem)); + Add(Intrinsic.Arm64UmullV, new IntrinsicInfo(0x2e20c000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UqaddS, new IntrinsicInfo(0x7e200c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64UqaddV, new IntrinsicInfo(0x2e200c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UqrshlS, new IntrinsicInfo(0x7e205c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64UqrshlV, new IntrinsicInfo(0x2e205c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UqrshrnS, new IntrinsicInfo(0x7f009c00u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64UqrshrnV, new IntrinsicInfo(0x2f009c00u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64UqshlSi, new IntrinsicInfo(0x7f007400u, IntrinsicType.ScalarBinaryShl)); + Add(Intrinsic.Arm64UqshlVi, new IntrinsicInfo(0x2f007400u, IntrinsicType.VectorBinaryShl)); + Add(Intrinsic.Arm64UqshlS, new IntrinsicInfo(0x7e204c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64UqshlV, new IntrinsicInfo(0x2e204c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UqshrnS, new IntrinsicInfo(0x7f009400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64UqshrnV, new IntrinsicInfo(0x2f009400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64UqsubS, new IntrinsicInfo(0x7e202c00u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64UqsubV, new IntrinsicInfo(0x2e202c00u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UqxtnS, new IntrinsicInfo(0x7e214800u, IntrinsicType.ScalarBinaryRd)); + Add(Intrinsic.Arm64UqxtnV, new IntrinsicInfo(0x2e214800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64UrecpeV, new IntrinsicInfo(0x0ea1c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64UrhaddV, new IntrinsicInfo(0x2e201400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UrshlS, new IntrinsicInfo(0x7e205400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64UrshlV, new IntrinsicInfo(0x2e205400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UrshrS, new IntrinsicInfo(0x7f002400u, IntrinsicType.ScalarBinaryShr)); + Add(Intrinsic.Arm64UrshrV, new IntrinsicInfo(0x2f002400u, IntrinsicType.VectorBinaryShr)); + Add(Intrinsic.Arm64UrsqrteV, new IntrinsicInfo(0x2ea1c800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64UrsraS, new IntrinsicInfo(0x7f003400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64UrsraV, new IntrinsicInfo(0x2f003400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64UshllV, new IntrinsicInfo(0x2f00a400u, IntrinsicType.VectorBinaryShl)); + Add(Intrinsic.Arm64UshlS, new IntrinsicInfo(0x7e204400u, IntrinsicType.ScalarBinary)); + Add(Intrinsic.Arm64UshlV, new IntrinsicInfo(0x2e204400u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UshrS, new IntrinsicInfo(0x7f000400u, IntrinsicType.ScalarBinaryShr)); + Add(Intrinsic.Arm64UshrV, new IntrinsicInfo(0x2f000400u, IntrinsicType.VectorBinaryShr)); + Add(Intrinsic.Arm64UsqaddS, new IntrinsicInfo(0x7e203800u, IntrinsicType.ScalarBinaryRd)); + Add(Intrinsic.Arm64UsqaddV, new IntrinsicInfo(0x2e203800u, IntrinsicType.VectorBinaryRd)); + Add(Intrinsic.Arm64UsraS, new IntrinsicInfo(0x7f001400u, IntrinsicType.ScalarTernaryShrRd)); + Add(Intrinsic.Arm64UsraV, new IntrinsicInfo(0x2f001400u, IntrinsicType.VectorTernaryShrRd)); + Add(Intrinsic.Arm64UsublV, new IntrinsicInfo(0x2e202000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64UsubwV, new IntrinsicInfo(0x2e203000u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64Uzp1V, new IntrinsicInfo(0x0e001800u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64Uzp2V, new IntrinsicInfo(0x0e005800u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64XtnV, new IntrinsicInfo(0x0e212800u, IntrinsicType.VectorUnary)); + Add(Intrinsic.Arm64Zip1V, new IntrinsicInfo(0x0e003800u, IntrinsicType.VectorBinary)); + Add(Intrinsic.Arm64Zip2V, new IntrinsicInfo(0x0e007800u, IntrinsicType.VectorBinary)); + } + + private static void Add(Intrinsic intrin, IntrinsicInfo info) + { + _intrinTable[(int)intrin] = info; + } + + public static IntrinsicInfo GetInfo(Intrinsic intrin) + { + return _intrinTable[(int)intrin]; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/Arm64/IntrinsicType.cs b/src/ARMeilleure/CodeGen/Arm64/IntrinsicType.cs new file mode 100644 index 00000000..800eca93 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/IntrinsicType.cs @@ -0,0 +1,59 @@ +namespace ARMeilleure.CodeGen.Arm64 +{ + enum IntrinsicType + { + ScalarUnary, + ScalarUnaryByElem, + ScalarBinary, + ScalarBinaryByElem, + ScalarBinaryFPByElem, + ScalarBinaryRd, + ScalarBinaryShl, + ScalarBinaryShr, + ScalarFcsel, + ScalarFmovi, + ScalarFPCompare, + ScalarFPCompareCond, + ScalarFPConv, + ScalarFPConvFixed, + ScalarFPConvFixedGpr, + ScalarFPConvGpr, + ScalarTernary, + ScalarTernaryFPRdByElem, + ScalarTernaryShlRd, + ScalarTernaryShrRd, + + VectorUnary, + VectorUnaryBitwise, + VectorUnaryByElem, + VectorBinary, + VectorBinaryBitwise, + VectorBinaryBitwiseImm, + VectorBinaryByElem, + VectorBinaryFPByElem, + VectorBinaryRd, + VectorBinaryShl, + VectorBinaryShr, + VectorExt, + VectorFmovi, + VectorFPConvFixed, + VectorInsertByElem, + VectorLdSt, + VectorLdStSs, + VectorLookupTable, + VectorMovi, + VectorMvni, + VectorTernaryFPRdByElem, + VectorTernaryRd, + VectorTernaryRdBitwise, + VectorTernaryRdByElem, + VectorTernaryShlRd, + VectorTernaryShrRd, + + Vector128Unary, + Vector128Binary, + + GetRegister, + SetRegister + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/Arm64/PreAllocator.cs b/src/ARMeilleure/CodeGen/Arm64/PreAllocator.cs new file mode 100644 index 00000000..6ea9d239 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Arm64/PreAllocator.cs @@ -0,0 +1,892 @@ +using ARMeilleure.CodeGen.RegisterAllocators; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.Arm64 +{ + static class PreAllocator + { + private class ConstantDict + { + private readonly Dictionary<(ulong, OperandType), Operand> _constants; + + public ConstantDict() + { + _constants = new Dictionary<(ulong, OperandType), Operand>(); + } + + public void Add(ulong value, OperandType type, Operand local) + { + _constants.Add((value, type), local); + } + + public bool TryGetValue(ulong value, OperandType type, out Operand local) + { + return _constants.TryGetValue((value, type), out local); + } + } + + public static void RunPass(CompilerContext cctx, StackAllocator stackAlloc, out int maxCallArgs) + { + maxCallArgs = -1; + + Span buffer = default; + + Operand[] preservedArgs = new Operand[CallingConvention.GetArgumentsOnRegsCount()]; + + for (BasicBlock block = cctx.Cfg.Blocks.First; block != null; block = block.ListNext) + { + ConstantDict constants = new ConstantDict(); + + Operation nextNode; + + for (Operation node = block.Operations.First; node != default; node = nextNode) + { + nextNode = node.ListNext; + + if (node.Instruction == Instruction.Phi) + { + continue; + } + + InsertConstantRegCopies(constants, block.Operations, node); + InsertDestructiveRegCopies(block.Operations, node); + + switch (node.Instruction) + { + case Instruction.Call: + // Get the maximum number of arguments used on a call. + // On windows, when a struct is returned from the call, + // we also need to pass the pointer where the struct + // should be written on the first argument. + int argsCount = node.SourcesCount - 1; + + if (node.Destination != default && node.Destination.Type == OperandType.V128) + { + argsCount++; + } + + if (maxCallArgs < argsCount) + { + maxCallArgs = argsCount; + } + + // Copy values to registers expected by the function + // being called, as mandated by the ABI. + InsertCallCopies(constants, block.Operations, node); + break; + case Instruction.CompareAndSwap: + case Instruction.CompareAndSwap16: + case Instruction.CompareAndSwap8: + nextNode = GenerateCompareAndSwap(block.Operations, node); + break; + case Instruction.LoadArgument: + nextNode = InsertLoadArgumentCopy(cctx, ref buffer, block.Operations, preservedArgs, node); + break; + case Instruction.Return: + InsertReturnCopy(block.Operations, node); + break; + case Instruction.Tailcall: + InsertTailcallCopies(constants, block.Operations, stackAlloc, node, node); + break; + } + } + } + } + + private static void InsertConstantRegCopies(ConstantDict constants, IntrusiveList nodes, Operation node) + { + if (node.SourcesCount == 0 || IsIntrinsicWithConst(node)) + { + return; + } + + Instruction inst = node.Instruction; + + Operand src1 = node.GetSource(0); + Operand src2; + + if (src1.Kind == OperandKind.Constant) + { + if (!src1.Type.IsInteger()) + { + // Handle non-integer types (FP32, FP64 and V128). + // For instructions without an immediate operand, we do the following: + // - Insert a copy with the constant value (as integer) to a GPR. + // - Insert a copy from the GPR to a XMM register. + // - Replace the constant use with the XMM register. + src1 = AddFloatConstantCopy(constants, nodes, node, src1); + + node.SetSource(0, src1); + } + else if (!HasConstSrc1(node, src1.Value)) + { + // Handle integer types. + // Most ALU instructions accepts a 32-bits immediate on the second operand. + // We need to ensure the following: + // - If the constant is on operand 1, we need to move it. + // -- But first, we try to swap operand 1 and 2 if the instruction is commutative. + // -- Doing so may allow us to encode the constant as operand 2 and avoid a copy. + // - If the constant is on operand 2, we check if the instruction supports it, + // if not, we also add a copy. 64-bits constants are usually not supported. + if (IsCommutative(node)) + { + src2 = node.GetSource(1); + + Operand temp = src1; + + src1 = src2; + src2 = temp; + + node.SetSource(0, src1); + node.SetSource(1, src2); + } + + if (src1.Kind == OperandKind.Constant) + { + src1 = AddIntConstantCopy(constants, nodes, node, src1); + + node.SetSource(0, src1); + } + } + } + + if (node.SourcesCount < 2) + { + return; + } + + src2 = node.GetSource(1); + + if (src2.Kind == OperandKind.Constant) + { + if (!src2.Type.IsInteger()) + { + src2 = AddFloatConstantCopy(constants, nodes, node, src2); + + node.SetSource(1, src2); + } + else if (!HasConstSrc2(inst, src2)) + { + src2 = AddIntConstantCopy(constants, nodes, node, src2); + + node.SetSource(1, src2); + } + } + + if (node.SourcesCount < 3 || + node.Instruction == Instruction.BranchIf || + node.Instruction == Instruction.Compare || + node.Instruction == Instruction.VectorInsert || + node.Instruction == Instruction.VectorInsert16 || + node.Instruction == Instruction.VectorInsert8) + { + return; + } + + for (int srcIndex = 2; srcIndex < node.SourcesCount; srcIndex++) + { + Operand src = node.GetSource(srcIndex); + + if (src.Kind == OperandKind.Constant) + { + if (!src.Type.IsInteger()) + { + src = AddFloatConstantCopy(constants, nodes, node, src); + + node.SetSource(srcIndex, src); + } + else + { + src = AddIntConstantCopy(constants, nodes, node, src); + + node.SetSource(srcIndex, src); + } + } + } + } + + private static void InsertDestructiveRegCopies(IntrusiveList nodes, Operation node) + { + if (node.Destination == default || node.SourcesCount == 0) + { + return; + } + + Operand dest = node.Destination; + Operand src1 = node.GetSource(0); + + if (IsSameOperandDestSrc1(node) && src1.Kind == OperandKind.LocalVariable) + { + bool useNewLocal = false; + + for (int srcIndex = 1; srcIndex < node.SourcesCount; srcIndex++) + { + if (node.GetSource(srcIndex) == dest) + { + useNewLocal = true; + + break; + } + } + + if (useNewLocal) + { + // Dest is being used as some source already, we need to use a new + // local to store the temporary value, otherwise the value on dest + // local would be overwritten. + Operand temp = Local(dest.Type); + + nodes.AddBefore(node, Operation(Instruction.Copy, temp, src1)); + + node.SetSource(0, temp); + + nodes.AddAfter(node, Operation(Instruction.Copy, dest, temp)); + + node.Destination = temp; + } + else + { + nodes.AddBefore(node, Operation(Instruction.Copy, dest, src1)); + + node.SetSource(0, dest); + } + } + } + + private static void InsertCallCopies(ConstantDict constants, IntrusiveList nodes, Operation node) + { + Operation operation = node; + + Operand dest = operation.Destination; + + List sources = new List + { + operation.GetSource(0) + }; + + int argsCount = operation.SourcesCount - 1; + + int intMax = CallingConvention.GetArgumentsOnRegsCount(); + int vecMax = CallingConvention.GetArgumentsOnRegsCount(); + + int intCount = 0; + int vecCount = 0; + + int stackOffset = 0; + + for (int index = 0; index < argsCount; index++) + { + Operand source = operation.GetSource(index + 1); + + bool passOnReg; + + if (source.Type.IsInteger()) + { + passOnReg = intCount < intMax; + } + else if (source.Type == OperandType.V128) + { + passOnReg = intCount + 1 < intMax; + } + else + { + passOnReg = vecCount < vecMax; + } + + if (source.Type == OperandType.V128 && passOnReg) + { + // V128 is a struct, we pass each half on a GPR if possible. + Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1))); + + continue; + } + + if (passOnReg) + { + Operand argReg = source.Type.IsInteger() + ? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type) + : Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type); + + Operation copyOp = Operation(Instruction.Copy, argReg, source); + + InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, copyOp)); + + sources.Add(argReg); + } + else + { + Operand offset = Const(stackOffset); + + Operation spillOp = Operation(Instruction.SpillArg, default, offset, source); + + InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, spillOp)); + + stackOffset += source.Type.GetSizeInBytes(); + } + } + + if (dest != default) + { + if (dest.Type == OperandType.V128) + { + Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64); + Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64); + + node = nodes.AddAfter(node, Operation(Instruction.VectorCreateScalar, dest, retLReg)); + nodes.AddAfter(node, Operation(Instruction.VectorInsert, dest, dest, retHReg, Const(1))); + + operation.Destination = default; + } + else + { + Operand retReg = dest.Type.IsInteger() + ? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type) + : Xmm(CallingConvention.GetVecReturnRegister(), dest.Type); + + Operation copyOp = Operation(Instruction.Copy, dest, retReg); + + nodes.AddAfter(node, copyOp); + + operation.Destination = retReg; + } + } + + operation.SetSources(sources.ToArray()); + } + + private static void InsertTailcallCopies( + ConstantDict constants, + IntrusiveList nodes, + StackAllocator stackAlloc, + Operation node, + Operation operation) + { + List sources = new List + { + operation.GetSource(0) + }; + + int argsCount = operation.SourcesCount - 1; + + int intMax = CallingConvention.GetArgumentsOnRegsCount(); + int vecMax = CallingConvention.GetArgumentsOnRegsCount(); + + int intCount = 0; + int vecCount = 0; + + // Handle arguments passed on registers. + for (int index = 0; index < argsCount; index++) + { + Operand source = operation.GetSource(1 + index); + + bool passOnReg; + + if (source.Type.IsInteger()) + { + passOnReg = intCount + 1 < intMax; + } + else + { + passOnReg = vecCount < vecMax; + } + + if (source.Type == OperandType.V128 && passOnReg) + { + // V128 is a struct, we pass each half on a GPR if possible. + Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1))); + + continue; + } + + if (passOnReg) + { + Operand argReg = source.Type.IsInteger() + ? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type) + : Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type); + + Operation copyOp = Operation(Instruction.Copy, argReg, source); + + InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, copyOp)); + + sources.Add(argReg); + } + else + { + throw new NotImplementedException("Spilling is not currently supported for tail calls. (too many arguments)"); + } + } + + // The target address must be on the return registers, since we + // don't return anything and it is guaranteed to not be a + // callee saved register (which would be trashed on the epilogue). + Operand tcAddress = Gpr(CodeGenCommon.TcAddressRegister, OperandType.I64); + + Operation addrCopyOp = Operation(Instruction.Copy, tcAddress, operation.GetSource(0)); + + nodes.AddBefore(node, addrCopyOp); + + sources[0] = tcAddress; + + operation.SetSources(sources.ToArray()); + } + + private static Operation GenerateCompareAndSwap(IntrusiveList nodes, Operation node) + { + Operand expected = node.GetSource(1); + + if (expected.Type == OperandType.V128) + { + Operand dest = node.Destination; + Operand expectedLow = Local(OperandType.I64); + Operand expectedHigh = Local(OperandType.I64); + Operand desiredLow = Local(OperandType.I64); + Operand desiredHigh = Local(OperandType.I64); + Operand actualLow = Local(OperandType.I64); + Operand actualHigh = Local(OperandType.I64); + + Operand address = node.GetSource(0); + Operand desired = node.GetSource(2); + + void SplitOperand(Operand source, Operand low, Operand high) + { + nodes.AddBefore(node, Operation(Instruction.VectorExtract, low, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, high, source, Const(1))); + } + + SplitOperand(expected, expectedLow, expectedHigh); + SplitOperand(desired, desiredLow, desiredHigh); + + Operation operation = node; + + // Update the sources and destinations with split 64-bit halfs of the whole 128-bit values. + // We also need a additional registers that will be used to store temporary information. + operation.SetDestinations(new[] { actualLow, actualHigh, Local(OperandType.I64), Local(OperandType.I64) }); + operation.SetSources(new[] { address, expectedLow, expectedHigh, desiredLow, desiredHigh }); + + // Add some dummy uses of the input operands, as the CAS operation will be a loop, + // so they can't be used as destination operand. + for (int i = 0; i < operation.SourcesCount; i++) + { + Operand src = operation.GetSource(i); + node = nodes.AddAfter(node, Operation(Instruction.Copy, src, src)); + } + + // Assemble the vector with the 64-bit values at the given memory location. + node = nodes.AddAfter(node, Operation(Instruction.VectorCreateScalar, dest, actualLow)); + node = nodes.AddAfter(node, Operation(Instruction.VectorInsert, dest, dest, actualHigh, Const(1))); + } + else + { + // We need a additional register where the store result will be written to. + node.SetDestinations(new[] { node.Destination, Local(OperandType.I32) }); + + // Add some dummy uses of the input operands, as the CAS operation will be a loop, + // so they can't be used as destination operand. + Operation operation = node; + + for (int i = 0; i < operation.SourcesCount; i++) + { + Operand src = operation.GetSource(i); + node = nodes.AddAfter(node, Operation(Instruction.Copy, src, src)); + } + } + + return node.ListNext; + } + + private static void InsertReturnCopy(IntrusiveList nodes, Operation node) + { + if (node.SourcesCount == 0) + { + return; + } + + Operand source = node.GetSource(0); + + if (source.Type == OperandType.V128) + { + Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64); + Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64); + + nodes.AddBefore(node, Operation(Instruction.VectorExtract, retLReg, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, retHReg, source, Const(1))); + } + else + { + Operand retReg = source.Type.IsInteger() + ? Gpr(CallingConvention.GetIntReturnRegister(), source.Type) + : Xmm(CallingConvention.GetVecReturnRegister(), source.Type); + + Operation retCopyOp = Operation(Instruction.Copy, retReg, source); + + nodes.AddBefore(node, retCopyOp); + } + } + + private static Operation InsertLoadArgumentCopy( + CompilerContext cctx, + ref Span buffer, + IntrusiveList nodes, + Operand[] preservedArgs, + Operation node) + { + Operand source = node.GetSource(0); + + Debug.Assert(source.Kind == OperandKind.Constant, "Non-constant LoadArgument source kind."); + + int index = source.AsInt32(); + + int intCount = 0; + int vecCount = 0; + + for (int cIndex = 0; cIndex < index; cIndex++) + { + OperandType argType = cctx.FuncArgTypes[cIndex]; + + if (argType.IsInteger()) + { + intCount++; + } + else if (argType == OperandType.V128) + { + intCount += 2; + } + else + { + vecCount++; + } + } + + bool passOnReg; + + if (source.Type.IsInteger()) + { + passOnReg = intCount < CallingConvention.GetArgumentsOnRegsCount(); + } + else if (source.Type == OperandType.V128) + { + passOnReg = intCount + 1 < CallingConvention.GetArgumentsOnRegsCount(); + } + else + { + passOnReg = vecCount < CallingConvention.GetArgumentsOnRegsCount(); + } + + if (passOnReg) + { + Operand dest = node.Destination; + + if (preservedArgs[index] == default) + { + if (dest.Type == OperandType.V128) + { + // V128 is a struct, we pass each half on a GPR if possible. + Operand pArg = Local(OperandType.V128); + + Operand argLReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount), OperandType.I64); + Operand argHReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount + 1), OperandType.I64); + + Operation copyL = Operation(Instruction.VectorCreateScalar, pArg, argLReg); + Operation copyH = Operation(Instruction.VectorInsert, pArg, pArg, argHReg, Const(1)); + + cctx.Cfg.Entry.Operations.AddFirst(copyH); + cctx.Cfg.Entry.Operations.AddFirst(copyL); + + preservedArgs[index] = pArg; + } + else + { + Operand pArg = Local(dest.Type); + + Operand argReg = dest.Type.IsInteger() + ? Gpr(CallingConvention.GetIntArgumentRegister(intCount), dest.Type) + : Xmm(CallingConvention.GetVecArgumentRegister(vecCount), dest.Type); + + Operation copyOp = Operation(Instruction.Copy, pArg, argReg); + + cctx.Cfg.Entry.Operations.AddFirst(copyOp); + + preservedArgs[index] = pArg; + } + } + + Operation nextNode; + + if (dest.AssignmentsCount == 1) + { + // Let's propagate the argument if we can to avoid copies. + PreAllocatorCommon.Propagate(ref buffer, dest, preservedArgs[index]); + nextNode = node.ListNext; + } + else + { + Operation argCopyOp = Operation(Instruction.Copy, dest, preservedArgs[index]); + nextNode = nodes.AddBefore(node, argCopyOp); + } + + Delete(nodes, node); + return nextNode; + } + else + { + // TODO: Pass on stack. + return node; + } + } + + private static Operand AddFloatConstantCopy( + ConstantDict constants, + IntrusiveList nodes, + Operation node, + Operand source) + { + Operand temp = Local(source.Type); + + Operand intConst = AddIntConstantCopy(constants, nodes, node, GetIntConst(source)); + + Operation copyOp = Operation(Instruction.VectorCreateScalar, temp, intConst); + + nodes.AddBefore(node, copyOp); + + return temp; + } + + private static Operand AddIntConstantCopy( + ConstantDict constants, + IntrusiveList nodes, + Operation node, + Operand source) + { + if (constants.TryGetValue(source.Value, source.Type, out Operand temp)) + { + return temp; + } + + temp = Local(source.Type); + + Operation copyOp = Operation(Instruction.Copy, temp, source); + + nodes.AddBefore(node, copyOp); + + constants.Add(source.Value, source.Type, temp); + + return temp; + } + + private static Operand GetIntConst(Operand value) + { + if (value.Type == OperandType.FP32) + { + return Const(value.AsInt32()); + } + else if (value.Type == OperandType.FP64) + { + return Const(value.AsInt64()); + } + + return value; + } + + private static void Delete(IntrusiveList nodes, Operation node) + { + node.Destination = default; + + for (int index = 0; index < node.SourcesCount; index++) + { + node.SetSource(index, default); + } + + nodes.Remove(node); + } + + private static Operand Gpr(int register, OperandType type) + { + return Register(register, RegisterType.Integer, type); + } + + private static Operand Xmm(int register, OperandType type) + { + return Register(register, RegisterType.Vector, type); + } + + private static bool IsSameOperandDestSrc1(Operation operation) + { + switch (operation.Instruction) + { + case Instruction.Extended: + return IsSameOperandDestSrc1(operation.Intrinsic); + case Instruction.VectorInsert: + case Instruction.VectorInsert16: + case Instruction.VectorInsert8: + return true; + } + + return false; + } + + private static bool IsSameOperandDestSrc1(Intrinsic intrinsic) + { + IntrinsicInfo info = IntrinsicTable.GetInfo(intrinsic & ~(Intrinsic.Arm64VTypeMask | Intrinsic.Arm64VSizeMask)); + + return info.Type == IntrinsicType.ScalarBinaryRd || + info.Type == IntrinsicType.ScalarTernaryFPRdByElem || + info.Type == IntrinsicType.ScalarTernaryShlRd || + info.Type == IntrinsicType.ScalarTernaryShrRd || + info.Type == IntrinsicType.VectorBinaryRd || + info.Type == IntrinsicType.VectorInsertByElem || + info.Type == IntrinsicType.VectorTernaryRd || + info.Type == IntrinsicType.VectorTernaryRdBitwise || + info.Type == IntrinsicType.VectorTernaryFPRdByElem || + info.Type == IntrinsicType.VectorTernaryRdByElem || + info.Type == IntrinsicType.VectorTernaryShlRd || + info.Type == IntrinsicType.VectorTernaryShrRd; + } + + private static bool HasConstSrc1(Operation node, ulong value) + { + switch (node.Instruction) + { + case Instruction.Add: + case Instruction.BranchIf: + case Instruction.Compare: + case Instruction.Subtract: + // The immediate encoding of those instructions does not allow Rn to be + // XZR (it will be SP instead), so we can't allow a Rn constant in this case. + return value == 0 && NotConstOrConst0(node.GetSource(1)); + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseNot: + case Instruction.BitwiseOr: + case Instruction.ByteSwap: + case Instruction.CountLeadingZeros: + case Instruction.Multiply: + case Instruction.Negate: + case Instruction.RotateRight: + case Instruction.ShiftLeft: + case Instruction.ShiftRightSI: + case Instruction.ShiftRightUI: + return value == 0; + case Instruction.Copy: + case Instruction.LoadArgument: + case Instruction.Spill: + case Instruction.SpillArg: + return true; + case Instruction.Extended: + return value == 0; + } + + return false; + } + + private static bool NotConstOrConst0(Operand operand) + { + return operand.Kind != OperandKind.Constant || operand.Value == 0; + } + + private static bool HasConstSrc2(Instruction inst, Operand operand) + { + ulong value = operand.Value; + + switch (inst) + { + case Instruction.Add: + case Instruction.BranchIf: + case Instruction.Compare: + case Instruction.Subtract: + return ConstFitsOnUImm12Sh(value); + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseOr: + return value == 0 || CodeGenCommon.TryEncodeBitMask(operand, out _, out _, out _); + case Instruction.Multiply: + case Instruction.Store: + case Instruction.Store16: + case Instruction.Store8: + return value == 0; + case Instruction.RotateRight: + case Instruction.ShiftLeft: + case Instruction.ShiftRightSI: + case Instruction.ShiftRightUI: + case Instruction.VectorExtract: + case Instruction.VectorExtract16: + case Instruction.VectorExtract8: + return true; + case Instruction.Extended: + // TODO: Check if actual intrinsic is supposed to have consts here? + // Right now we only hit this case for fixed-point int <-> FP conversion instructions. + return true; + } + + return false; + } + + private static bool IsCommutative(Operation operation) + { + switch (operation.Instruction) + { + case Instruction.Add: + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseOr: + case Instruction.Multiply: + return true; + + case Instruction.BranchIf: + case Instruction.Compare: + { + Operand comp = operation.GetSource(2); + + Debug.Assert(comp.Kind == OperandKind.Constant); + + var compType = (Comparison)comp.AsInt32(); + + return compType == Comparison.Equal || compType == Comparison.NotEqual; + } + } + + return false; + } + + private static bool ConstFitsOnUImm12Sh(ulong value) + { + return (value & ~0xfffUL) == 0 || (value & ~0xfff000UL) == 0; + } + + private static bool IsIntrinsicWithConst(Operation operation) + { + bool isIntrinsic = IsIntrinsic(operation.Instruction); + + if (isIntrinsic) + { + Intrinsic intrinsic = operation.Intrinsic; + IntrinsicInfo info = IntrinsicTable.GetInfo(intrinsic & ~(Intrinsic.Arm64VTypeMask | Intrinsic.Arm64VSizeMask)); + + // Those have integer inputs that don't support consts. + return info.Type != IntrinsicType.ScalarFPConvGpr && + info.Type != IntrinsicType.ScalarFPConvFixedGpr && + info.Type != IntrinsicType.SetRegister; + } + + return false; + } + + private static bool IsIntrinsic(Instruction inst) + { + return inst == Instruction.Extended; + } + } +} diff --git a/src/ARMeilleure/CodeGen/CompiledFunction.cs b/src/ARMeilleure/CodeGen/CompiledFunction.cs new file mode 100644 index 00000000..0560bf2e --- /dev/null +++ b/src/ARMeilleure/CodeGen/CompiledFunction.cs @@ -0,0 +1,68 @@ +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.CodeGen.Unwinding; +using ARMeilleure.Translation.Cache; +using System; +using System.Runtime.InteropServices; + +namespace ARMeilleure.CodeGen +{ + /// + /// Represents a compiled function. + /// + readonly struct CompiledFunction + { + /// + /// Gets the machine code of the . + /// + public byte[] Code { get; } + + /// + /// Gets the of the . + /// + public UnwindInfo UnwindInfo { get; } + + /// + /// Gets the of the . + /// + public RelocInfo RelocInfo { get; } + + /// + /// Initializes a new instance of the struct with the specified machine code, + /// unwind info and relocation info. + /// + /// Machine code + /// Unwind info + /// Relocation info + internal CompiledFunction(byte[] code, UnwindInfo unwindInfo, RelocInfo relocInfo) + { + Code = code; + UnwindInfo = unwindInfo; + RelocInfo = relocInfo; + } + + /// + /// Maps the onto the and returns a delegate of type + /// pointing to the mapped function. + /// + /// Type of delegate + /// A delegate of type pointing to the mapped function + public T Map() + { + return MapWithPointer(out _); + } + + /// + /// Maps the onto the and returns a delegate of type + /// pointing to the mapped function. + /// + /// Type of delegate + /// Pointer to the function code in memory + /// A delegate of type pointing to the mapped function + public T MapWithPointer(out IntPtr codePointer) + { + codePointer = JitCache.Map(this); + + return Marshal.GetDelegateForFunctionPointer(codePointer); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/Linking/RelocEntry.cs b/src/ARMeilleure/CodeGen/Linking/RelocEntry.cs new file mode 100644 index 00000000..a27bfded --- /dev/null +++ b/src/ARMeilleure/CodeGen/Linking/RelocEntry.cs @@ -0,0 +1,38 @@ +namespace ARMeilleure.CodeGen.Linking +{ + /// + /// Represents a relocation. + /// + readonly struct RelocEntry + { + public const int Stride = 13; // Bytes. + + /// + /// Gets the position of the relocation. + /// + public int Position { get; } + + /// + /// Gets the of the relocation. + /// + public Symbol Symbol { get; } + + /// + /// Initializes a new instance of the struct with the specified position and + /// . + /// + /// Position of relocation + /// Symbol of relocation + public RelocEntry(int position, Symbol symbol) + { + Position = position; + Symbol = symbol; + } + + /// + public override string ToString() + { + return $"({nameof(Position)} = {Position}, {nameof(Symbol)} = {Symbol})"; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/Linking/RelocInfo.cs b/src/ARMeilleure/CodeGen/Linking/RelocInfo.cs new file mode 100644 index 00000000..caaf08e3 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Linking/RelocInfo.cs @@ -0,0 +1,32 @@ +using System; + +namespace ARMeilleure.CodeGen.Linking +{ + /// + /// Represents relocation information about a . + /// + readonly struct RelocInfo + { + /// + /// Gets an empty . + /// + public static RelocInfo Empty { get; } = new RelocInfo(null); + + private readonly RelocEntry[] _entries; + + /// + /// Gets the set of . + /// + public ReadOnlySpan Entries => _entries; + + /// + /// Initializes a new instance of the struct with the specified set of + /// . + /// + /// Set of to use + public RelocInfo(RelocEntry[] entries) + { + _entries = entries; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/Linking/Symbol.cs b/src/ARMeilleure/CodeGen/Linking/Symbol.cs new file mode 100644 index 00000000..39e0c3eb --- /dev/null +++ b/src/ARMeilleure/CodeGen/Linking/Symbol.cs @@ -0,0 +1,99 @@ +using System; + +namespace ARMeilleure.CodeGen.Linking +{ + /// + /// Represents a symbol. + /// + readonly struct Symbol + { + private readonly ulong _value; + + /// + /// Gets the of the . + /// + public SymbolType Type { get; } + + /// + /// Gets the value of the . + /// + /// is + public ulong Value + { + get + { + if (Type == SymbolType.None) + { + ThrowSymbolNone(); + } + + return _value; + } + } + + /// + /// Initializes a new instance of the structure with the specified and value. + /// + /// Type of symbol + /// Value of symbol + public Symbol(SymbolType type, ulong value) + { + (Type, _value) = (type, value); + } + + /// + /// Determines if the specified instances are equal. + /// + /// First instance + /// Second instance + /// if equal; otherwise + public static bool operator ==(Symbol a, Symbol b) + { + return a.Equals(b); + } + + /// + /// Determines if the specified instances are not equal. + /// + /// First instance + /// Second instance + /// if not equal; otherwise + public static bool operator !=(Symbol a, Symbol b) + { + return !(a == b); + } + + /// + /// Determines if the specified is equal to this instance. + /// + /// Other instance + /// if equal; otherwise + public bool Equals(Symbol other) + { + return other.Type == Type && other._value == _value; + } + + /// + public override bool Equals(object obj) + { + return obj is Symbol sym && Equals(sym); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Type, _value); + } + + /// + public override string ToString() + { + return $"{Type}:{_value}"; + } + + private static void ThrowSymbolNone() + { + throw new InvalidOperationException("Symbol refers to nothing."); + } + } +} diff --git a/src/ARMeilleure/CodeGen/Linking/SymbolType.cs b/src/ARMeilleure/CodeGen/Linking/SymbolType.cs new file mode 100644 index 00000000..b05b6969 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Linking/SymbolType.cs @@ -0,0 +1,28 @@ +namespace ARMeilleure.CodeGen.Linking +{ + /// + /// Types of . + /// + enum SymbolType : byte + { + /// + /// Refers to nothing, i.e no symbol. + /// + None, + + /// + /// Refers to an entry in . + /// + DelegateTable, + + /// + /// Refers to an entry in . + /// + FunctionTable, + + /// + /// Refers to a special symbol which is handled by . + /// + Special + } +} diff --git a/src/ARMeilleure/CodeGen/Optimizations/BlockPlacement.cs b/src/ARMeilleure/CodeGen/Optimizations/BlockPlacement.cs new file mode 100644 index 00000000..9e243d37 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Optimizations/BlockPlacement.cs @@ -0,0 +1,72 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System.Diagnostics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.CodeGen.Optimizations +{ + static class BlockPlacement + { + public static void RunPass(ControlFlowGraph cfg) + { + bool update = false; + + BasicBlock block; + BasicBlock nextBlock; + + BasicBlock lastBlock = cfg.Blocks.Last; + + // Move cold blocks at the end of the list, so that they are emitted away from hot code. + for (block = cfg.Blocks.First; block != null; block = nextBlock) + { + nextBlock = block.ListNext; + + if (block.Frequency == BasicBlockFrequency.Cold) + { + cfg.Blocks.Remove(block); + cfg.Blocks.AddLast(block); + } + + if (block == lastBlock) + { + break; + } + } + + for (block = cfg.Blocks.First; block != null; block = nextBlock) + { + nextBlock = block.ListNext; + + if (block.SuccessorsCount == 2) + { + Operation branchOp = block.Operations.Last; + + Debug.Assert(branchOp.Instruction == Instruction.BranchIf); + + BasicBlock falseSucc = block.GetSuccessor(0); + BasicBlock trueSucc = block.GetSuccessor(1); + + // If true successor is next block in list, invert the condition. We avoid extra branching by + // making the true side the fallthrough (i.e, convert it to the false side). + if (trueSucc == block.ListNext) + { + Comparison comp = (Comparison)branchOp.GetSource(2).AsInt32(); + Comparison compInv = comp.Invert(); + + branchOp.SetSource(2, Const((int)compInv)); + + block.SetSuccessor(0, trueSucc); + block.SetSuccessor(1, falseSucc); + + update = true; + } + } + } + + if (update) + { + cfg.Update(); + } + } + } +} diff --git a/src/ARMeilleure/CodeGen/Optimizations/ConstantFolding.cs b/src/ARMeilleure/CodeGen/Optimizations/ConstantFolding.cs new file mode 100644 index 00000000..c5a22a53 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Optimizations/ConstantFolding.cs @@ -0,0 +1,346 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.CodeGen.Optimizations +{ + static class ConstantFolding + { + public static void RunPass(Operation operation) + { + if (operation.Destination == default || operation.SourcesCount == 0) + { + return; + } + + if (!AreAllSourcesConstant(operation)) + { + return; + } + + OperandType type = operation.Destination.Type; + + switch (operation.Instruction) + { + case Instruction.Add: + if (operation.GetSource(0).Relocatable || + operation.GetSource(1).Relocatable) + { + break; + } + + if (type == OperandType.I32) + { + EvaluateBinaryI32(operation, (x, y) => x + y); + } + else if (type == OperandType.I64) + { + EvaluateBinaryI64(operation, (x, y) => x + y); + } + break; + + case Instruction.BitwiseAnd: + if (type == OperandType.I32) + { + EvaluateBinaryI32(operation, (x, y) => x & y); + } + else if (type == OperandType.I64) + { + EvaluateBinaryI64(operation, (x, y) => x & y); + } + break; + + case Instruction.BitwiseExclusiveOr: + if (type == OperandType.I32) + { + EvaluateBinaryI32(operation, (x, y) => x ^ y); + } + else if (type == OperandType.I64) + { + EvaluateBinaryI64(operation, (x, y) => x ^ y); + } + break; + + case Instruction.BitwiseNot: + if (type == OperandType.I32) + { + EvaluateUnaryI32(operation, (x) => ~x); + } + else if (type == OperandType.I64) + { + EvaluateUnaryI64(operation, (x) => ~x); + } + break; + + case Instruction.BitwiseOr: + if (type == OperandType.I32) + { + EvaluateBinaryI32(operation, (x, y) => x | y); + } + else if (type == OperandType.I64) + { + EvaluateBinaryI64(operation, (x, y) => x | y); + } + break; + + case Instruction.ConvertI64ToI32: + if (type == OperandType.I32) + { + EvaluateUnaryI32(operation, (x) => x); + } + break; + + case Instruction.Compare: + if (type == OperandType.I32 && + operation.GetSource(0).Type == type && + operation.GetSource(1).Type == type) + { + switch ((Comparison)operation.GetSource(2).Value) + { + case Comparison.Equal: + EvaluateBinaryI32(operation, (x, y) => x == y ? 1 : 0); + break; + case Comparison.NotEqual: + EvaluateBinaryI32(operation, (x, y) => x != y ? 1 : 0); + break; + case Comparison.Greater: + EvaluateBinaryI32(operation, (x, y) => x > y ? 1 : 0); + break; + case Comparison.LessOrEqual: + EvaluateBinaryI32(operation, (x, y) => x <= y ? 1 : 0); + break; + case Comparison.GreaterUI: + EvaluateBinaryI32(operation, (x, y) => (uint)x > (uint)y ? 1 : 0); + break; + case Comparison.LessOrEqualUI: + EvaluateBinaryI32(operation, (x, y) => (uint)x <= (uint)y ? 1 : 0); + break; + case Comparison.GreaterOrEqual: + EvaluateBinaryI32(operation, (x, y) => x >= y ? 1 : 0); + break; + case Comparison.Less: + EvaluateBinaryI32(operation, (x, y) => x < y ? 1 : 0); + break; + case Comparison.GreaterOrEqualUI: + EvaluateBinaryI32(operation, (x, y) => (uint)x >= (uint)y ? 1 : 0); + break; + case Comparison.LessUI: + EvaluateBinaryI32(operation, (x, y) => (uint)x < (uint)y ? 1 : 0); + break; + } + } + break; + + case Instruction.Copy: + if (type == OperandType.I32) + { + EvaluateUnaryI32(operation, (x) => x); + } + else if (type == OperandType.I64) + { + EvaluateUnaryI64(operation, (x) => x); + } + break; + + case Instruction.Divide: + if (type == OperandType.I32) + { + EvaluateBinaryI32(operation, (x, y) => y != 0 ? x / y : 0); + } + else if (type == OperandType.I64) + { + EvaluateBinaryI64(operation, (x, y) => y != 0 ? x / y : 0); + } + break; + + case Instruction.DivideUI: + if (type == OperandType.I32) + { + EvaluateBinaryI32(operation, (x, y) => y != 0 ? (int)((uint)x / (uint)y) : 0); + } + else if (type == OperandType.I64) + { + EvaluateBinaryI64(operation, (x, y) => y != 0 ? (long)((ulong)x / (ulong)y) : 0); + } + break; + + case Instruction.Multiply: + if (type == OperandType.I32) + { + EvaluateBinaryI32(operation, (x, y) => x * y); + } + else if (type == OperandType.I64) + { + EvaluateBinaryI64(operation, (x, y) => x * y); + } + break; + + case Instruction.Negate: + if (type == OperandType.I32) + { + EvaluateUnaryI32(operation, (x) => -x); + } + else if (type == OperandType.I64) + { + EvaluateUnaryI64(operation, (x) => -x); + } + break; + + case Instruction.ShiftLeft: + if (type == OperandType.I32) + { + EvaluateBinaryI32(operation, (x, y) => x << y); + } + else if (type == OperandType.I64) + { + EvaluateBinaryI64(operation, (x, y) => x << (int)y); + } + break; + + case Instruction.ShiftRightSI: + if (type == OperandType.I32) + { + EvaluateBinaryI32(operation, (x, y) => x >> y); + } + else if (type == OperandType.I64) + { + EvaluateBinaryI64(operation, (x, y) => x >> (int)y); + } + break; + + case Instruction.ShiftRightUI: + if (type == OperandType.I32) + { + EvaluateBinaryI32(operation, (x, y) => (int)((uint)x >> y)); + } + else if (type == OperandType.I64) + { + EvaluateBinaryI64(operation, (x, y) => (long)((ulong)x >> (int)y)); + } + break; + + case Instruction.SignExtend16: + if (type == OperandType.I32) + { + EvaluateUnaryI32(operation, (x) => (short)x); + } + else if (type == OperandType.I64) + { + EvaluateUnaryI64(operation, (x) => (short)x); + } + break; + + case Instruction.SignExtend32: + if (type == OperandType.I32) + { + EvaluateUnaryI32(operation, (x) => x); + } + else if (type == OperandType.I64) + { + EvaluateUnaryI64(operation, (x) => (int)x); + } + break; + + case Instruction.SignExtend8: + if (type == OperandType.I32) + { + EvaluateUnaryI32(operation, (x) => (sbyte)x); + } + else if (type == OperandType.I64) + { + EvaluateUnaryI64(operation, (x) => (sbyte)x); + } + break; + + case Instruction.ZeroExtend16: + if (type == OperandType.I32) + { + EvaluateUnaryI32(operation, (x) => (ushort)x); + } + else if (type == OperandType.I64) + { + EvaluateUnaryI64(operation, (x) => (ushort)x); + } + break; + + case Instruction.ZeroExtend32: + if (type == OperandType.I32) + { + EvaluateUnaryI32(operation, (x) => x); + } + else if (type == OperandType.I64) + { + EvaluateUnaryI64(operation, (x) => (uint)x); + } + break; + + case Instruction.ZeroExtend8: + if (type == OperandType.I32) + { + EvaluateUnaryI32(operation, (x) => (byte)x); + } + else if (type == OperandType.I64) + { + EvaluateUnaryI64(operation, (x) => (byte)x); + } + break; + + case Instruction.Subtract: + if (type == OperandType.I32) + { + EvaluateBinaryI32(operation, (x, y) => x - y); + } + else if (type == OperandType.I64) + { + EvaluateBinaryI64(operation, (x, y) => x - y); + } + break; + } + } + + private static bool AreAllSourcesConstant(Operation operation) + { + for (int index = 0; index < operation.SourcesCount; index++) + { + Operand srcOp = operation.GetSource(index); + + if (srcOp.Kind != OperandKind.Constant) + { + return false; + } + } + + return true; + } + + private static void EvaluateUnaryI32(Operation operation, Func op) + { + int x = operation.GetSource(0).AsInt32(); + + operation.TurnIntoCopy(Const(op(x))); + } + + private static void EvaluateUnaryI64(Operation operation, Func op) + { + long x = operation.GetSource(0).AsInt64(); + + operation.TurnIntoCopy(Const(op(x))); + } + + private static void EvaluateBinaryI32(Operation operation, Func op) + { + int x = operation.GetSource(0).AsInt32(); + int y = operation.GetSource(1).AsInt32(); + + operation.TurnIntoCopy(Const(op(x, y))); + } + + private static void EvaluateBinaryI64(Operation operation, Func op) + { + long x = operation.GetSource(0).AsInt64(); + long y = operation.GetSource(1).AsInt64(); + + operation.TurnIntoCopy(Const(op(x, y))); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/Optimizations/Optimizer.cs b/src/ARMeilleure/CodeGen/Optimizations/Optimizer.cs new file mode 100644 index 00000000..a45bb455 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Optimizations/Optimizer.cs @@ -0,0 +1,252 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.CodeGen.Optimizations +{ + static class Optimizer + { + public static void RunPass(ControlFlowGraph cfg) + { + // Scratch buffer used to store uses. + Span buffer = default; + + bool modified; + + do + { + modified = false; + + for (BasicBlock block = cfg.Blocks.Last; block != null; block = block.ListPrevious) + { + Operation node; + Operation prevNode; + + for (node = block.Operations.Last; node != default; node = prevNode) + { + prevNode = node.ListPrevious; + + if (IsUnused(node)) + { + RemoveNode(block, node); + + modified = true; + + continue; + } + else if (node.Instruction == Instruction.Phi) + { + continue; + } + + ConstantFolding.RunPass(node); + Simplification.RunPass(node); + + if (DestIsSingleLocalVar(node)) + { + if (IsPropagableCompare(node)) + { + modified |= PropagateCompare(ref buffer, node); + + if (modified && IsUnused(node)) + { + RemoveNode(block, node); + } + } + else if (IsPropagableCopy(node)) + { + PropagateCopy(ref buffer, node); + + RemoveNode(block, node); + + modified = true; + } + } + } + } + } + while (modified); + } + + public static void RemoveUnusedNodes(ControlFlowGraph cfg) + { + bool modified; + + do + { + modified = false; + + for (BasicBlock block = cfg.Blocks.Last; block != null; block = block.ListPrevious) + { + Operation node; + Operation prevNode; + + for (node = block.Operations.Last; node != default; node = prevNode) + { + prevNode = node.ListPrevious; + + if (IsUnused(node)) + { + RemoveNode(block, node); + + modified = true; + } + } + } + } + while (modified); + } + + private static bool PropagateCompare(ref Span buffer, Operation compOp) + { + // Try to propagate Compare operations into their BranchIf uses, when these BranchIf uses are in the form + // of: + // + // - BranchIf %x, 0x0, Equal ;; i.e BranchIfFalse %x + // - BranchIf %x, 0x0, NotEqual ;; i.e BranchIfTrue %x + // + // The commutative property of Equal and NotEqual is taken into consideration as well. + // + // For example: + // + // %x = Compare %a, %b, comp + // BranchIf %x, 0x0, NotEqual + // + // => + // + // BranchIf %a, %b, comp + + static bool IsZeroBranch(Operation operation, out Comparison compType) + { + compType = Comparison.Equal; + + if (operation.Instruction != Instruction.BranchIf) + { + return false; + } + + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand comp = operation.GetSource(2); + + compType = (Comparison)comp.AsInt32(); + + return (src1.Kind == OperandKind.Constant && src1.Value == 0) || + (src2.Kind == OperandKind.Constant && src2.Value == 0); + } + + bool modified = false; + + Operand dest = compOp.Destination; + Operand src1 = compOp.GetSource(0); + Operand src2 = compOp.GetSource(1); + Operand comp = compOp.GetSource(2); + + Comparison compType = (Comparison)comp.AsInt32(); + + Span uses = dest.GetUses(ref buffer); + + foreach (Operation use in uses) + { + // If operation is a BranchIf and has a constant value 0 in its RHS or LHS source operands. + if (IsZeroBranch(use, out Comparison otherCompType)) + { + Comparison propCompType; + + if (otherCompType == Comparison.NotEqual) + { + propCompType = compType; + } + else if (otherCompType == Comparison.Equal) + { + propCompType = compType.Invert(); + } + else + { + continue; + } + + use.SetSource(0, src1); + use.SetSource(1, src2); + use.SetSource(2, Const((int)propCompType)); + + modified = true; + } + } + + return modified; + } + + private static void PropagateCopy(ref Span buffer, Operation copyOp) + { + // Propagate copy source operand to all uses of the destination operand. + Operand dest = copyOp.Destination; + Operand source = copyOp.GetSource(0); + + Span uses = dest.GetUses(ref buffer); + + foreach (Operation use in uses) + { + for (int index = 0; index < use.SourcesCount; index++) + { + if (use.GetSource(index) == dest) + { + use.SetSource(index, source); + } + } + } + } + + private static void RemoveNode(BasicBlock block, Operation node) + { + // Remove a node from the nodes list, and also remove itself + // from all the use lists on the operands that this node uses. + block.Operations.Remove(node); + + for (int index = 0; index < node.SourcesCount; index++) + { + node.SetSource(index, default); + } + + Debug.Assert(node.Destination == default || node.Destination.UsesCount == 0); + + node.Destination = default; + } + + private static bool IsUnused(Operation node) + { + return DestIsSingleLocalVar(node) && node.Destination.UsesCount == 0 && !HasSideEffects(node); + } + + private static bool DestIsSingleLocalVar(Operation node) + { + return node.DestinationsCount == 1 && node.Destination.Kind == OperandKind.LocalVariable; + } + + private static bool HasSideEffects(Operation node) + { + return node.Instruction == Instruction.Call + || node.Instruction == Instruction.Tailcall + || node.Instruction == Instruction.CompareAndSwap + || node.Instruction == Instruction.CompareAndSwap16 + || node.Instruction == Instruction.CompareAndSwap8; + } + + private static bool IsPropagableCompare(Operation operation) + { + return operation.Instruction == Instruction.Compare; + } + + private static bool IsPropagableCopy(Operation operation) + { + if (operation.Instruction != Instruction.Copy) + { + return false; + } + + return operation.Destination.Type == operation.GetSource(0).Type; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/Optimizations/Simplification.cs b/src/ARMeilleure/CodeGen/Optimizations/Simplification.cs new file mode 100644 index 00000000..a439d642 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Optimizations/Simplification.cs @@ -0,0 +1,183 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.CodeGen.Optimizations +{ + static class Simplification + { + public static void RunPass(Operation operation) + { + switch (operation.Instruction) + { + case Instruction.Add: + if (operation.GetSource(0).Relocatable || + operation.GetSource(1).Relocatable) + { + break; + } + + TryEliminateBinaryOpComutative(operation, 0); + break; + + case Instruction.BitwiseAnd: + TryEliminateBitwiseAnd(operation); + break; + + case Instruction.BitwiseOr: + TryEliminateBitwiseOr(operation); + break; + + case Instruction.BitwiseExclusiveOr: + TryEliminateBitwiseExclusiveOr(operation); + break; + + case Instruction.ConditionalSelect: + TryEliminateConditionalSelect(operation); + break; + + case Instruction.Divide: + TryEliminateBinaryOpY(operation, 1); + break; + + case Instruction.Multiply: + TryEliminateBinaryOpComutative(operation, 1); + break; + + case Instruction.ShiftLeft: + case Instruction.ShiftRightSI: + case Instruction.ShiftRightUI: + case Instruction.Subtract: + TryEliminateBinaryOpY(operation, 0); + break; + } + } + + private static void TryEliminateBitwiseAnd(Operation operation) + { + // Try to recognize and optimize those 3 patterns (in order): + // x & 0xFFFFFFFF == x, 0xFFFFFFFF & y == y, + // x & 0x00000000 == 0x00000000, 0x00000000 & y == 0x00000000 + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(x, AllOnes(x.Type))) + { + operation.TurnIntoCopy(y); + } + else if (IsConstEqual(y, AllOnes(y.Type))) + { + operation.TurnIntoCopy(x); + } + else if (IsConstEqual(x, 0) || IsConstEqual(y, 0)) + { + operation.TurnIntoCopy(Const(x.Type, 0)); + } + } + + private static void TryEliminateBitwiseOr(Operation operation) + { + // Try to recognize and optimize those 3 patterns (in order): + // x | 0x00000000 == x, 0x00000000 | y == y, + // x | 0xFFFFFFFF == 0xFFFFFFFF, 0xFFFFFFFF | y == 0xFFFFFFFF + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(x, 0)) + { + operation.TurnIntoCopy(y); + } + else if (IsConstEqual(y, 0)) + { + operation.TurnIntoCopy(x); + } + else if (IsConstEqual(x, AllOnes(x.Type)) || IsConstEqual(y, AllOnes(y.Type))) + { + operation.TurnIntoCopy(Const(AllOnes(x.Type))); + } + } + + private static void TryEliminateBitwiseExclusiveOr(Operation operation) + { + // Try to recognize and optimize those 2 patterns (in order): + // x ^ y == 0x00000000 when x == y + // 0x00000000 ^ y == y, x ^ 0x00000000 == x + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (x == y && x.Type.IsInteger()) + { + operation.TurnIntoCopy(Const(x.Type, 0)); + } + else + { + TryEliminateBinaryOpComutative(operation, 0); + } + } + + private static void TryEliminateBinaryOpY(Operation operation, ulong comparand) + { + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(y, comparand)) + { + operation.TurnIntoCopy(x); + } + } + + private static void TryEliminateBinaryOpComutative(Operation operation, ulong comparand) + { + Operand x = operation.GetSource(0); + Operand y = operation.GetSource(1); + + if (IsConstEqual(x, comparand)) + { + operation.TurnIntoCopy(y); + } + else if (IsConstEqual(y, comparand)) + { + operation.TurnIntoCopy(x); + } + } + + private static void TryEliminateConditionalSelect(Operation operation) + { + Operand cond = operation.GetSource(0); + + if (cond.Kind != OperandKind.Constant) + { + return; + } + + // The condition is constant, we can turn it into a copy, and select + // the source based on the condition value. + int srcIndex = cond.Value != 0 ? 1 : 2; + + Operand source = operation.GetSource(srcIndex); + + operation.TurnIntoCopy(source); + } + + private static bool IsConstEqual(Operand operand, ulong comparand) + { + if (operand.Kind != OperandKind.Constant || !operand.Type.IsInteger()) + { + return false; + } + + return operand.Value == comparand; + } + + private static ulong AllOnes(OperandType type) + { + switch (type) + { + case OperandType.I32: return ~0U; + case OperandType.I64: return ~0UL; + } + + throw new ArgumentException("Invalid operand type \"" + type + "\"."); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/Optimizations/TailMerge.cs b/src/ARMeilleure/CodeGen/Optimizations/TailMerge.cs new file mode 100644 index 00000000..e94df159 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Optimizations/TailMerge.cs @@ -0,0 +1,83 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.Optimizations +{ + static class TailMerge + { + public static void RunPass(in CompilerContext cctx) + { + ControlFlowGraph cfg = cctx.Cfg; + + BasicBlock mergedReturn = new(cfg.Blocks.Count); + + Operand returnValue; + Operation returnOp; + + if (cctx.FuncReturnType == OperandType.None) + { + returnValue = default; + returnOp = Operation(Instruction.Return, default); + } + else + { + returnValue = cfg.AllocateLocal(cctx.FuncReturnType); + returnOp = Operation(Instruction.Return, default, returnValue); + } + + mergedReturn.Frequency = BasicBlockFrequency.Cold; + mergedReturn.Operations.AddLast(returnOp); + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + Operation op = block.Operations.Last; + + if (op != default && op.Instruction == Instruction.Return) + { + block.Operations.Remove(op); + + if (cctx.FuncReturnType == OperandType.None) + { + PrepareMerge(block, mergedReturn); + } + else + { + Operation copyOp = Operation(Instruction.Copy, returnValue, op.GetSource(0)); + + PrepareMerge(block, mergedReturn).Append(copyOp); + } + } + } + + cfg.Blocks.AddLast(mergedReturn); + cfg.Update(); + } + + private static BasicBlock PrepareMerge(BasicBlock from, BasicBlock to) + { + BasicBlock fromPred = from.Predecessors.Count == 1 ? from.Predecessors[0] : null; + + // If the block is empty, we can try to append to the predecessor and avoid unnecessary jumps. + if (from.Operations.Count == 0 && fromPred != null && fromPred.SuccessorsCount == 1) + { + for (int i = 0; i < fromPred.SuccessorsCount; i++) + { + if (fromPred.GetSuccessor(i) == from) + { + fromPred.SetSuccessor(i, to); + } + } + + // NOTE: `from` becomes unreachable and the call to `cfg.Update()` will remove it. + return fromPred; + } + else + { + from.AddSuccessor(to); + + return from; + } + } + } +} diff --git a/src/ARMeilleure/CodeGen/PreAllocatorCommon.cs b/src/ARMeilleure/CodeGen/PreAllocatorCommon.cs new file mode 100644 index 00000000..53f279fb --- /dev/null +++ b/src/ARMeilleure/CodeGen/PreAllocatorCommon.cs @@ -0,0 +1,57 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.CodeGen +{ + static class PreAllocatorCommon + { + public static void Propagate(ref Span buffer, Operand dest, Operand value) + { + ReadOnlySpan uses = dest.GetUses(ref buffer); + + foreach (Operation use in uses) + { + for (int srcIndex = 0; srcIndex < use.SourcesCount; srcIndex++) + { + Operand useSrc = use.GetSource(srcIndex); + + if (useSrc == dest) + { + use.SetSource(srcIndex, value); + } + else if (useSrc.Kind == OperandKind.Memory) + { + MemoryOperand memoryOp = useSrc.GetMemory(); + + Operand baseAddr = memoryOp.BaseAddress; + Operand index = memoryOp.Index; + bool changed = false; + + if (baseAddr == dest) + { + baseAddr = value; + changed = true; + } + + if (index == dest) + { + index = value; + changed = true; + } + + if (changed) + { + use.SetSource(srcIndex, MemoryOp( + useSrc.Type, + baseAddr, + index, + memoryOp.Scale, + memoryOp.Displacement)); + } + } + } + } + } + } +} diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/AllocationResult.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/AllocationResult.cs new file mode 100644 index 00000000..43e5c7e2 --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/AllocationResult.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + readonly struct AllocationResult + { + public int IntUsedRegisters { get; } + public int VecUsedRegisters { get; } + public int SpillRegionSize { get; } + + public AllocationResult( + int intUsedRegisters, + int vecUsedRegisters, + int spillRegionSize) + { + IntUsedRegisters = intUsedRegisters; + VecUsedRegisters = vecUsedRegisters; + SpillRegionSize = spillRegionSize; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/CopyResolver.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/CopyResolver.cs new file mode 100644 index 00000000..587b1a02 --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/CopyResolver.cs @@ -0,0 +1,259 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Collections.Generic; + +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + class CopyResolver + { + private class ParallelCopy + { + private readonly struct Copy + { + public Register Dest { get; } + public Register Source { get; } + + public OperandType Type { get; } + + public Copy(Register dest, Register source, OperandType type) + { + Dest = dest; + Source = source; + Type = type; + } + } + + private readonly List _copies; + + public int Count => _copies.Count; + + public ParallelCopy() + { + _copies = new List(); + } + + public void AddCopy(Register dest, Register source, OperandType type) + { + _copies.Add(new Copy(dest, source, type)); + } + + public void Sequence(List sequence) + { + Dictionary locations = new Dictionary(); + Dictionary sources = new Dictionary(); + + Dictionary types = new Dictionary(); + + Queue pendingQueue = new Queue(); + Queue readyQueue = new Queue(); + + foreach (Copy copy in _copies) + { + locations[copy.Source] = copy.Source; + sources[copy.Dest] = copy.Source; + types[copy.Dest] = copy.Type; + + pendingQueue.Enqueue(copy.Dest); + } + + foreach (Copy copy in _copies) + { + // If the destination is not used anywhere, we can assign it immediately. + if (!locations.ContainsKey(copy.Dest)) + { + readyQueue.Enqueue(copy.Dest); + } + } + + while (pendingQueue.TryDequeue(out Register current)) + { + Register copyDest; + Register origSource; + Register copySource; + + while (readyQueue.TryDequeue(out copyDest)) + { + origSource = sources[copyDest]; + copySource = locations[origSource]; + + OperandType type = types[copyDest]; + + EmitCopy(sequence, GetRegister(copyDest, type), GetRegister(copySource, type)); + + locations[origSource] = copyDest; + + if (origSource == copySource && sources.ContainsKey(origSource)) + { + readyQueue.Enqueue(origSource); + } + } + + copyDest = current; + origSource = sources[copyDest]; + copySource = locations[origSource]; + + if (copyDest != copySource) + { + OperandType type = types[copyDest]; + + type = type.IsInteger() ? OperandType.I64 : OperandType.V128; + + EmitXorSwap(sequence, GetRegister(copyDest, type), GetRegister(copySource, type)); + + locations[origSource] = copyDest; + + Register swapOther = copySource; + + if (copyDest != locations[sources[copySource]]) + { + // Find the other swap destination register. + // To do that, we search all the pending registers, and pick + // the one where the copy source register is equal to the + // current destination register being processed (copyDest). + foreach (Register pending in pendingQueue) + { + // Is this a copy of pending <- copyDest? + if (copyDest == locations[sources[pending]]) + { + swapOther = pending; + + break; + } + } + } + + // The value that was previously at "copyDest" now lives on + // "copySource" thanks to the swap, now we need to update the + // location for the next copy that is supposed to copy the value + // that used to live on "copyDest". + locations[sources[swapOther]] = copySource; + } + } + } + + private static void EmitCopy(List sequence, Operand x, Operand y) + { + sequence.Add(Operation(Instruction.Copy, x, y)); + } + + private static void EmitXorSwap(List sequence, Operand x, Operand y) + { + sequence.Add(Operation(Instruction.BitwiseExclusiveOr, x, x, y)); + sequence.Add(Operation(Instruction.BitwiseExclusiveOr, y, y, x)); + sequence.Add(Operation(Instruction.BitwiseExclusiveOr, x, x, y)); + } + } + + private Queue _fillQueue = null; + private Queue _spillQueue = null; + private ParallelCopy _parallelCopy = null; + + public bool HasCopy { get; private set; } + + public void AddSplit(LiveInterval left, LiveInterval right) + { + if (left.Local != right.Local) + { + throw new ArgumentException("Intervals of different variables are not allowed."); + } + + OperandType type = left.Local.Type; + + if (left.IsSpilled && !right.IsSpilled) + { + // Move from the stack to a register. + AddSplitFill(left, right, type); + } + else if (!left.IsSpilled && right.IsSpilled) + { + // Move from a register to the stack. + AddSplitSpill(left, right, type); + } + else if (!left.IsSpilled && !right.IsSpilled && left.Register != right.Register) + { + // Move from one register to another. + AddSplitCopy(left, right, type); + } + else if (left.SpillOffset != right.SpillOffset) + { + // This would be the stack-to-stack move case, but this is not supported. + throw new ArgumentException("Both intervals were spilled."); + } + } + + private void AddSplitFill(LiveInterval left, LiveInterval right, OperandType type) + { + if (_fillQueue == null) + { + _fillQueue = new Queue(); + } + + Operand register = GetRegister(right.Register, type); + Operand offset = Const(left.SpillOffset); + + _fillQueue.Enqueue(Operation(Instruction.Fill, register, offset)); + + HasCopy = true; + } + + private void AddSplitSpill(LiveInterval left, LiveInterval right, OperandType type) + { + if (_spillQueue == null) + { + _spillQueue = new Queue(); + } + + Operand offset = Const(right.SpillOffset); + Operand register = GetRegister(left.Register, type); + + _spillQueue.Enqueue(Operation(Instruction.Spill, default, offset, register)); + + HasCopy = true; + } + + private void AddSplitCopy(LiveInterval left, LiveInterval right, OperandType type) + { + if (_parallelCopy == null) + { + _parallelCopy = new ParallelCopy(); + } + + _parallelCopy.AddCopy(right.Register, left.Register, type); + + HasCopy = true; + } + + public Operation[] Sequence() + { + List sequence = new List(); + + if (_spillQueue != null) + { + while (_spillQueue.TryDequeue(out Operation spillOp)) + { + sequence.Add(spillOp); + } + } + + _parallelCopy?.Sequence(sequence); + + if (_fillQueue != null) + { + while (_fillQueue.TryDequeue(out Operation fillOp)) + { + sequence.Add(fillOp); + } + } + + return sequence.ToArray(); + } + + private static Operand GetRegister(Register reg, OperandType type) + { + return Register(reg.Index, reg.Type, type); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/HybridAllocator.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/HybridAllocator.cs new file mode 100644 index 00000000..25952c77 --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/HybridAllocator.cs @@ -0,0 +1,454 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + class HybridAllocator : IRegisterAllocator + { + private readonly struct BlockInfo + { + public bool HasCall { get; } + + public int IntFixedRegisters { get; } + public int VecFixedRegisters { get; } + + public BlockInfo(bool hasCall, int intFixedRegisters, int vecFixedRegisters) + { + HasCall = hasCall; + IntFixedRegisters = intFixedRegisters; + VecFixedRegisters = vecFixedRegisters; + } + } + + private struct LocalInfo + { + public int Uses { get; set; } + public int UsesAllocated { get; set; } + public int Sequence { get; set; } + public Operand Temp { get; set; } + public Operand Register { get; set; } + public Operand SpillOffset { get; set; } + public OperandType Type { get; } + + private int _first; + private int _last; + + public bool IsBlockLocal => _first == _last; + + public LocalInfo(OperandType type, int uses, int blkIndex) + { + Uses = uses; + Type = type; + + UsesAllocated = 0; + Sequence = 0; + Temp = default; + Register = default; + SpillOffset = default; + + _first = -1; + _last = -1; + + SetBlockIndex(blkIndex); + } + + public void SetBlockIndex(int blkIndex) + { + if (_first == -1 || blkIndex < _first) + { + _first = blkIndex; + } + + if (_last == -1 || blkIndex > _last) + { + _last = blkIndex; + } + } + } + + private const int MaxIROperands = 4; + // The "visited" state is stored in the MSB of the local's value. + private const ulong VisitedMask = 1ul << 63; + + private BlockInfo[] _blockInfo; + private LocalInfo[] _localInfo; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsVisited(Operand local) + { + Debug.Assert(local.Kind == OperandKind.LocalVariable); + + return (local.GetValueUnsafe() & VisitedMask) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void SetVisited(Operand local) + { + Debug.Assert(local.Kind == OperandKind.LocalVariable); + + local.GetValueUnsafe() |= VisitedMask; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ref LocalInfo GetLocalInfo(Operand local) + { + Debug.Assert(local.Kind == OperandKind.LocalVariable); + Debug.Assert(IsVisited(local), "Local variable not visited. Used before defined?"); + + return ref _localInfo[(uint)local.GetValueUnsafe() - 1]; + } + + public AllocationResult RunPass(ControlFlowGraph cfg, StackAllocator stackAlloc, RegisterMasks regMasks) + { + int intUsedRegisters = 0; + int vecUsedRegisters = 0; + + int intFreeRegisters = regMasks.IntAvailableRegisters; + int vecFreeRegisters = regMasks.VecAvailableRegisters; + + _blockInfo = new BlockInfo[cfg.Blocks.Count]; + _localInfo = new LocalInfo[cfg.Blocks.Count * 3]; + + int localInfoCount = 0; + + for (int index = cfg.PostOrderBlocks.Length - 1; index >= 0; index--) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + int intFixedRegisters = 0; + int vecFixedRegisters = 0; + + bool hasCall = false; + + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + if (node.Instruction == Instruction.Call) + { + hasCall = true; + } + + foreach (Operand source in node.SourcesUnsafe) + { + if (source.Kind == OperandKind.LocalVariable) + { + GetLocalInfo(source).SetBlockIndex(block.Index); + } + else if (source.Kind == OperandKind.Memory) + { + MemoryOperand memOp = source.GetMemory(); + + if (memOp.BaseAddress != default) + { + GetLocalInfo(memOp.BaseAddress).SetBlockIndex(block.Index); + } + + if (memOp.Index != default) + { + GetLocalInfo(memOp.Index).SetBlockIndex(block.Index); + } + } + } + + foreach (Operand dest in node.DestinationsUnsafe) + { + if (dest.Kind == OperandKind.LocalVariable) + { + if (IsVisited(dest)) + { + GetLocalInfo(dest).SetBlockIndex(block.Index); + } + else + { + dest.NumberLocal(++localInfoCount); + + if (localInfoCount > _localInfo.Length) + { + Array.Resize(ref _localInfo, localInfoCount * 2); + } + + SetVisited(dest); + GetLocalInfo(dest) = new LocalInfo(dest.Type, UsesCount(dest), block.Index); + } + } + else if (dest.Kind == OperandKind.Register) + { + if (dest.Type.IsInteger()) + { + intFixedRegisters |= 1 << dest.GetRegister().Index; + } + else + { + vecFixedRegisters |= 1 << dest.GetRegister().Index; + } + } + } + } + + _blockInfo[block.Index] = new BlockInfo(hasCall, intFixedRegisters, vecFixedRegisters); + } + + int sequence = 0; + + for (int index = cfg.PostOrderBlocks.Length - 1; index >= 0; index--) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + ref BlockInfo blkInfo = ref _blockInfo[block.Index]; + + int intLocalFreeRegisters = intFreeRegisters & ~blkInfo.IntFixedRegisters; + int vecLocalFreeRegisters = vecFreeRegisters & ~blkInfo.VecFixedRegisters; + + int intCallerSavedRegisters = blkInfo.HasCall ? regMasks.IntCallerSavedRegisters : 0; + int vecCallerSavedRegisters = blkInfo.HasCall ? regMasks.VecCallerSavedRegisters : 0; + + int intSpillTempRegisters = SelectSpillTemps( + intCallerSavedRegisters & ~blkInfo.IntFixedRegisters, + intLocalFreeRegisters); + int vecSpillTempRegisters = SelectSpillTemps( + vecCallerSavedRegisters & ~blkInfo.VecFixedRegisters, + vecLocalFreeRegisters); + + intLocalFreeRegisters &= ~(intSpillTempRegisters | intCallerSavedRegisters); + vecLocalFreeRegisters &= ~(vecSpillTempRegisters | vecCallerSavedRegisters); + + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + int intLocalUse = 0; + int vecLocalUse = 0; + + Operand AllocateRegister(Operand local) + { + ref LocalInfo info = ref GetLocalInfo(local); + + info.UsesAllocated++; + + Debug.Assert(info.UsesAllocated <= info.Uses); + + if (info.Register != default) + { + if (info.UsesAllocated == info.Uses) + { + Register reg = info.Register.GetRegister(); + + if (local.Type.IsInteger()) + { + intLocalFreeRegisters |= 1 << reg.Index; + } + else + { + vecLocalFreeRegisters |= 1 << reg.Index; + } + } + + return info.Register; + } + else + { + Operand temp = info.Temp; + + if (temp == default || info.Sequence != sequence) + { + temp = local.Type.IsInteger() + ? GetSpillTemp(local, intSpillTempRegisters, ref intLocalUse) + : GetSpillTemp(local, vecSpillTempRegisters, ref vecLocalUse); + + info.Sequence = sequence; + info.Temp = temp; + } + + Operation fillOp = Operation(Instruction.Fill, temp, info.SpillOffset); + + block.Operations.AddBefore(node, fillOp); + + return temp; + } + } + + bool folded = false; + + // If operation is a copy of a local and that local is living on the stack, we turn the copy into + // a fill, instead of inserting a fill before it. + if (node.Instruction == Instruction.Copy) + { + Operand source = node.GetSource(0); + + if (source.Kind == OperandKind.LocalVariable) + { + ref LocalInfo info = ref GetLocalInfo(source); + + if (info.Register == default) + { + Operation fillOp = Operation(Instruction.Fill, node.Destination, info.SpillOffset); + + block.Operations.AddBefore(node, fillOp); + block.Operations.Remove(node); + + node = fillOp; + + folded = true; + } + } + } + + if (!folded) + { + foreach (ref Operand source in node.SourcesUnsafe) + { + if (source.Kind == OperandKind.LocalVariable) + { + source = AllocateRegister(source); + } + else if (source.Kind == OperandKind.Memory) + { + MemoryOperand memOp = source.GetMemory(); + + if (memOp.BaseAddress != default) + { + memOp.BaseAddress = AllocateRegister(memOp.BaseAddress); + } + + if (memOp.Index != default) + { + memOp.Index = AllocateRegister(memOp.Index); + } + } + } + } + + int intLocalAsg = 0; + int vecLocalAsg = 0; + + foreach (ref Operand dest in node.DestinationsUnsafe) + { + if (dest.Kind != OperandKind.LocalVariable) + { + continue; + } + + ref LocalInfo info = ref GetLocalInfo(dest); + + if (info.UsesAllocated == 0) + { + int mask = dest.Type.IsInteger() + ? intLocalFreeRegisters + : vecLocalFreeRegisters; + + if (info.IsBlockLocal && mask != 0) + { + int selectedReg = BitOperations.TrailingZeroCount(mask); + + info.Register = Register(selectedReg, info.Type.ToRegisterType(), info.Type); + + if (dest.Type.IsInteger()) + { + intLocalFreeRegisters &= ~(1 << selectedReg); + intUsedRegisters |= 1 << selectedReg; + } + else + { + vecLocalFreeRegisters &= ~(1 << selectedReg); + vecUsedRegisters |= 1 << selectedReg; + } + } + else + { + info.Register = default; + info.SpillOffset = Const(stackAlloc.Allocate(dest.Type.GetSizeInBytes())); + } + } + + info.UsesAllocated++; + + Debug.Assert(info.UsesAllocated <= info.Uses); + + if (info.Register != default) + { + dest = info.Register; + } + else + { + Operand temp = info.Temp; + + if (temp == default || info.Sequence != sequence) + { + temp = dest.Type.IsInteger() + ? GetSpillTemp(dest, intSpillTempRegisters, ref intLocalAsg) + : GetSpillTemp(dest, vecSpillTempRegisters, ref vecLocalAsg); + + info.Sequence = sequence; + info.Temp = temp; + } + + dest = temp; + + Operation spillOp = Operation(Instruction.Spill, default, info.SpillOffset, temp); + + block.Operations.AddAfter(node, spillOp); + + node = spillOp; + } + } + + sequence++; + + intUsedRegisters |= intLocalAsg | intLocalUse; + vecUsedRegisters |= vecLocalAsg | vecLocalUse; + } + } + + return new AllocationResult(intUsedRegisters, vecUsedRegisters, stackAlloc.TotalSize); + } + + private static int SelectSpillTemps(int mask0, int mask1) + { + int selection = 0; + int count = 0; + + while (count < MaxIROperands && mask0 != 0) + { + int mask = mask0 & -mask0; + + selection |= mask; + + mask0 &= ~mask; + + count++; + } + + while (count < MaxIROperands && mask1 != 0) + { + int mask = mask1 & -mask1; + + selection |= mask; + + mask1 &= ~mask; + + count++; + } + + Debug.Assert(count == MaxIROperands, "No enough registers for spill temps."); + + return selection; + } + + private static Operand GetSpillTemp(Operand local, int freeMask, ref int useMask) + { + int selectedReg = BitOperations.TrailingZeroCount(freeMask & ~useMask); + + useMask |= 1 << selectedReg; + + return Register(selectedReg, local.Type.ToRegisterType(), local.Type); + } + + private static int UsesCount(Operand local) + { + return local.AssignmentsCount + local.UsesCount; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/IRegisterAllocator.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/IRegisterAllocator.cs new file mode 100644 index 00000000..8f236c25 --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/IRegisterAllocator.cs @@ -0,0 +1,12 @@ +using ARMeilleure.Translation; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + interface IRegisterAllocator + { + AllocationResult RunPass( + ControlFlowGraph cfg, + StackAllocator stackAlloc, + RegisterMasks regMasks); + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs new file mode 100644 index 00000000..d80157af --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs @@ -0,0 +1,1101 @@ +using ARMeilleure.Common; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Numerics; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + // Based on: + // "Linear Scan Register Allocation for the Java(tm) HotSpot Client Compiler". + // http://www.christianwimmer.at/Publications/Wimmer04a/Wimmer04a.pdf + class LinearScanAllocator : IRegisterAllocator + { + private const int InstructionGap = 2; + private const int InstructionGapMask = InstructionGap - 1; + + private HashSet _blockEdges; + private LiveRange[] _blockRanges; + private BitMap[] _blockLiveIn; + + private List _intervals; + private LiveInterval[] _parentIntervals; + + private List<(IntrusiveList, Operation)> _operationNodes; + private int _operationsCount; + + private class AllocationContext + { + public RegisterMasks Masks { get; } + + public StackAllocator StackAlloc { get; } + + public BitMap Active { get; } + public BitMap Inactive { get; } + + public int IntUsedRegisters { get; set; } + public int VecUsedRegisters { get; set; } + + private readonly int[] _intFreePositions; + private readonly int[] _vecFreePositions; + private readonly int _intFreePositionsCount; + private readonly int _vecFreePositionsCount; + + public AllocationContext(StackAllocator stackAlloc, RegisterMasks masks, int intervalsCount) + { + StackAlloc = stackAlloc; + Masks = masks; + + Active = new BitMap(Allocators.Default, intervalsCount); + Inactive = new BitMap(Allocators.Default, intervalsCount); + + PopulateFreePositions(RegisterType.Integer, out _intFreePositions, out _intFreePositionsCount); + PopulateFreePositions(RegisterType.Vector, out _vecFreePositions, out _vecFreePositionsCount); + + void PopulateFreePositions(RegisterType type, out int[] positions, out int count) + { + positions = new int[masks.RegistersCount]; + count = BitOperations.PopCount((uint)masks.GetAvailableRegisters(type)); + + int mask = masks.GetAvailableRegisters(type); + + for (int i = 0; i < positions.Length; i++) + { + if ((mask & (1 << i)) != 0) + { + positions[i] = int.MaxValue; + } + } + } + } + + public void GetFreePositions(RegisterType type, in Span positions, out int count) + { + if (type == RegisterType.Integer) + { + _intFreePositions.CopyTo(positions); + + count = _intFreePositionsCount; + } + else + { + Debug.Assert(type == RegisterType.Vector); + + _vecFreePositions.CopyTo(positions); + + count = _vecFreePositionsCount; + } + } + + public void MoveActiveToInactive(int bit) + { + Move(Active, Inactive, bit); + } + + public void MoveInactiveToActive(int bit) + { + Move(Inactive, Active, bit); + } + + private static void Move(BitMap source, BitMap dest, int bit) + { + source.Clear(bit); + + dest.Set(bit); + } + } + + public AllocationResult RunPass( + ControlFlowGraph cfg, + StackAllocator stackAlloc, + RegisterMasks regMasks) + { + NumberLocals(cfg, regMasks.RegistersCount); + + var context = new AllocationContext(stackAlloc, regMasks, _intervals.Count); + + BuildIntervals(cfg, context); + + for (int index = 0; index < _intervals.Count; index++) + { + LiveInterval current = _intervals[index]; + + if (current.IsEmpty) + { + continue; + } + + if (current.IsFixed) + { + context.Active.Set(index); + + if (current.IsFixedAndUsed) + { + if (current.Register.Type == RegisterType.Integer) + { + context.IntUsedRegisters |= 1 << current.Register.Index; + } + else /* if (interval.Register.Type == RegisterType.Vector) */ + { + context.VecUsedRegisters |= 1 << current.Register.Index; + } + } + + continue; + } + + AllocateInterval(context, current, index, regMasks.RegistersCount); + } + + for (int index = regMasks.RegistersCount * 2; index < _intervals.Count; index++) + { + if (!_intervals[index].IsSpilled) + { + ReplaceLocalWithRegister(_intervals[index]); + } + } + + InsertSplitCopies(); + InsertSplitCopiesAtEdges(cfg); + + return new AllocationResult(context.IntUsedRegisters, context.VecUsedRegisters, context.StackAlloc.TotalSize); + } + + private void AllocateInterval(AllocationContext context, LiveInterval current, int cIndex, int registersCount) + { + // Check active intervals that already ended. + foreach (int iIndex in context.Active) + { + LiveInterval interval = _intervals[iIndex]; + + interval.Forward(current.GetStart()); + + if (interval.GetEnd() < current.GetStart()) + { + context.Active.Clear(iIndex); + } + else if (!interval.Overlaps(current.GetStart())) + { + context.MoveActiveToInactive(iIndex); + } + } + + // Check inactive intervals that already ended or were reactivated. + foreach (int iIndex in context.Inactive) + { + LiveInterval interval = _intervals[iIndex]; + + interval.Forward(current.GetStart()); + + if (interval.GetEnd() < current.GetStart()) + { + context.Inactive.Clear(iIndex); + } + else if (interval.Overlaps(current.GetStart())) + { + context.MoveInactiveToActive(iIndex); + } + } + + if (!TryAllocateRegWithoutSpill(context, current, cIndex, registersCount)) + { + AllocateRegWithSpill(context, current, cIndex, registersCount); + } + } + + private bool TryAllocateRegWithoutSpill(AllocationContext context, LiveInterval current, int cIndex, int registersCount) + { + RegisterType regType = current.Local.Type.ToRegisterType(); + + Span freePositions = stackalloc int[registersCount]; + + context.GetFreePositions(regType, freePositions, out int freePositionsCount); + + foreach (int iIndex in context.Active) + { + LiveInterval interval = _intervals[iIndex]; + Register reg = interval.Register; + + if (reg.Type == regType) + { + freePositions[reg.Index] = 0; + freePositionsCount--; + } + } + + // If all registers are already active, return early. No point in inspecting the inactive set to look for + // holes. + if (freePositionsCount == 0) + { + return false; + } + + foreach (int iIndex in context.Inactive) + { + LiveInterval interval = _intervals[iIndex]; + Register reg = interval.Register; + + ref int freePosition = ref freePositions[reg.Index]; + + if (reg.Type == regType && freePosition != 0) + { + int overlapPosition = interval.GetOverlapPosition(current); + + if (overlapPosition != LiveInterval.NotFound && freePosition > overlapPosition) + { + freePosition = overlapPosition; + } + } + } + + int selectedReg = GetHighestValueIndex(freePositions); + int selectedNextUse = freePositions[selectedReg]; + + // Intervals starts and ends at odd positions, unless they span an entire + // block, in this case they will have ranges at a even position. + // When a interval is loaded from the stack to a register, we can only + // do the split at a odd position, because otherwise the split interval + // that is inserted on the list to be processed may clobber a register + // used by the instruction at the same position as the split. + // The problem only happens when a interval ends exactly at this instruction, + // because otherwise they would interfere, and the register wouldn't be selected. + // When the interval is aligned and the above happens, there's no problem as + // the instruction that is actually with the last use is the one + // before that position. + selectedNextUse &= ~InstructionGapMask; + + if (selectedNextUse <= current.GetStart()) + { + return false; + } + else if (selectedNextUse < current.GetEnd()) + { + LiveInterval splitChild = current.Split(selectedNextUse); + + if (splitChild.UsesCount != 0) + { + Debug.Assert(splitChild.GetStart() > current.GetStart(), "Split interval has an invalid start position."); + + InsertInterval(splitChild, registersCount); + } + else + { + Spill(context, splitChild); + } + } + + current.Register = new Register(selectedReg, regType); + + if (regType == RegisterType.Integer) + { + context.IntUsedRegisters |= 1 << selectedReg; + } + else /* if (regType == RegisterType.Vector) */ + { + context.VecUsedRegisters |= 1 << selectedReg; + } + + context.Active.Set(cIndex); + + return true; + } + + private void AllocateRegWithSpill(AllocationContext context, LiveInterval current, int cIndex, int registersCount) + { + RegisterType regType = current.Local.Type.ToRegisterType(); + + Span usePositions = stackalloc int[registersCount]; + Span blockedPositions = stackalloc int[registersCount]; + + context.GetFreePositions(regType, usePositions, out _); + context.GetFreePositions(regType, blockedPositions, out _); + + foreach (int iIndex in context.Active) + { + LiveInterval interval = _intervals[iIndex]; + Register reg = interval.Register; + + if (reg.Type == regType) + { + ref int usePosition = ref usePositions[reg.Index]; + ref int blockedPosition = ref blockedPositions[reg.Index]; + + if (interval.IsFixed) + { + usePosition = 0; + blockedPosition = 0; + } + else + { + int nextUse = interval.NextUseAfter(current.GetStart()); + + if (nextUse != LiveInterval.NotFound && usePosition > nextUse) + { + usePosition = nextUse; + } + } + } + } + + foreach (int iIndex in context.Inactive) + { + LiveInterval interval = _intervals[iIndex]; + Register reg = interval.Register; + + if (reg.Type == regType) + { + ref int usePosition = ref usePositions[reg.Index]; + ref int blockedPosition = ref blockedPositions[reg.Index]; + + if (interval.IsFixed) + { + int overlapPosition = interval.GetOverlapPosition(current); + + if (overlapPosition != LiveInterval.NotFound) + { + blockedPosition = Math.Min(blockedPosition, overlapPosition); + usePosition = Math.Min(usePosition, overlapPosition); + } + } + else if (interval.Overlaps(current)) + { + int nextUse = interval.NextUseAfter(current.GetStart()); + + if (nextUse != LiveInterval.NotFound && usePosition > nextUse) + { + usePosition = nextUse; + } + } + } + } + + int selectedReg = GetHighestValueIndex(usePositions); + int currentFirstUse = current.FirstUse(); + + Debug.Assert(currentFirstUse >= 0, "Current interval has no uses."); + + if (usePositions[selectedReg] < currentFirstUse) + { + // All intervals on inactive and active are being used before current, + // so spill the current interval. + Debug.Assert(currentFirstUse > current.GetStart(), "Trying to spill a interval currently being used."); + + LiveInterval splitChild = current.Split(currentFirstUse); + + Debug.Assert(splitChild.GetStart() > current.GetStart(), "Split interval has an invalid start position."); + + InsertInterval(splitChild, registersCount); + + Spill(context, current); + } + else if (blockedPositions[selectedReg] > current.GetEnd()) + { + // Spill made the register available for the entire current lifetime, + // so we only need to split the intervals using the selected register. + current.Register = new Register(selectedReg, regType); + + SplitAndSpillOverlappingIntervals(context, current, registersCount); + + context.Active.Set(cIndex); + } + else + { + // There are conflicts even after spill due to the use of fixed registers + // that can't be spilled, so we need to also split current at the point of + // the first fixed register use. + current.Register = new Register(selectedReg, regType); + + int splitPosition = blockedPositions[selectedReg] & ~InstructionGapMask; + + Debug.Assert(splitPosition > current.GetStart(), "Trying to split a interval at a invalid position."); + + LiveInterval splitChild = current.Split(splitPosition); + + if (splitChild.UsesCount != 0) + { + Debug.Assert(splitChild.GetStart() > current.GetStart(), "Split interval has an invalid start position."); + + InsertInterval(splitChild, registersCount); + } + else + { + Spill(context, splitChild); + } + + SplitAndSpillOverlappingIntervals(context, current, registersCount); + + context.Active.Set(cIndex); + } + } + + private static int GetHighestValueIndex(Span span) + { + int highest = int.MinValue; + + int selected = 0; + + for (int index = 0; index < span.Length; index++) + { + int current = span[index]; + + if (highest < current) + { + highest = current; + selected = index; + + if (current == int.MaxValue) + { + break; + } + } + } + + return selected; + } + + private void SplitAndSpillOverlappingIntervals(AllocationContext context, LiveInterval current, int registersCount) + { + foreach (int iIndex in context.Active) + { + LiveInterval interval = _intervals[iIndex]; + + if (!interval.IsFixed && interval.Register == current.Register) + { + SplitAndSpillOverlappingInterval(context, current, interval, registersCount); + + context.Active.Clear(iIndex); + } + } + + foreach (int iIndex in context.Inactive) + { + LiveInterval interval = _intervals[iIndex]; + + if (!interval.IsFixed && interval.Register == current.Register && interval.Overlaps(current)) + { + SplitAndSpillOverlappingInterval(context, current, interval, registersCount); + + context.Inactive.Clear(iIndex); + } + } + } + + private void SplitAndSpillOverlappingInterval( + AllocationContext context, + LiveInterval current, + LiveInterval interval, + int registersCount) + { + // If there's a next use after the start of the current interval, + // we need to split the spilled interval twice, and re-insert it + // on the "pending" list to ensure that it will get a new register + // on that use position. + int nextUse = interval.NextUseAfter(current.GetStart()); + + LiveInterval splitChild; + + if (interval.GetStart() < current.GetStart()) + { + splitChild = interval.Split(current.GetStart()); + } + else + { + splitChild = interval; + } + + if (nextUse != -1) + { + Debug.Assert(nextUse > current.GetStart(), "Trying to spill a interval currently being used."); + + if (nextUse > splitChild.GetStart()) + { + LiveInterval right = splitChild.Split(nextUse); + + Spill(context, splitChild); + + splitChild = right; + } + + InsertInterval(splitChild, registersCount); + } + else + { + Spill(context, splitChild); + } + } + + private void InsertInterval(LiveInterval interval, int registersCount) + { + Debug.Assert(interval.UsesCount != 0, "Trying to insert a interval without uses."); + Debug.Assert(!interval.IsEmpty, "Trying to insert a empty interval."); + Debug.Assert(!interval.IsSpilled, "Trying to insert a spilled interval."); + + int startIndex = registersCount * 2; + + int insertIndex = _intervals.BinarySearch(startIndex, _intervals.Count - startIndex, interval, null); + + if (insertIndex < 0) + { + insertIndex = ~insertIndex; + } + + _intervals.Insert(insertIndex, interval); + } + + private void Spill(AllocationContext context, LiveInterval interval) + { + Debug.Assert(!interval.IsFixed, "Trying to spill a fixed interval."); + Debug.Assert(interval.UsesCount == 0, "Trying to spill a interval with uses."); + + // We first check if any of the siblings were spilled, if so we can reuse + // the stack offset. Otherwise, we allocate a new space on the stack. + // This prevents stack-to-stack copies being necessary for a split interval. + if (!interval.TrySpillWithSiblingOffset()) + { + interval.Spill(context.StackAlloc.Allocate(interval.Local.Type)); + } + } + + private void InsertSplitCopies() + { + Dictionary copyResolvers = new Dictionary(); + + CopyResolver GetCopyResolver(int position) + { + if (!copyResolvers.TryGetValue(position, out CopyResolver copyResolver)) + { + copyResolver = new CopyResolver(); + + copyResolvers.Add(position, copyResolver); + } + + return copyResolver; + } + + foreach (LiveInterval interval in _intervals.Where(x => x.IsSplit)) + { + LiveInterval previous = interval; + + foreach (LiveInterval splitChild in interval.SplitChildren()) + { + int splitPosition = splitChild.GetStart(); + + if (!_blockEdges.Contains(splitPosition) && previous.GetEnd() == splitPosition) + { + GetCopyResolver(splitPosition).AddSplit(previous, splitChild); + } + + previous = splitChild; + } + } + + foreach (KeyValuePair kv in copyResolvers) + { + CopyResolver copyResolver = kv.Value; + + if (!copyResolver.HasCopy) + { + continue; + } + + int splitPosition = kv.Key; + + (IntrusiveList nodes, Operation node) = GetOperationNode(splitPosition); + + Operation[] sequence = copyResolver.Sequence(); + + nodes.AddBefore(node, sequence[0]); + + node = sequence[0]; + + for (int index = 1; index < sequence.Length; index++) + { + nodes.AddAfter(node, sequence[index]); + + node = sequence[index]; + } + } + } + + private void InsertSplitCopiesAtEdges(ControlFlowGraph cfg) + { + int blocksCount = cfg.Blocks.Count; + + bool IsSplitEdgeBlock(BasicBlock block) + { + return block.Index >= blocksCount; + } + + // Reset iterators to beginning because GetSplitChild depends on the state of the iterator. + foreach (LiveInterval interval in _intervals) + { + interval.Reset(); + } + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + if (IsSplitEdgeBlock(block)) + { + continue; + } + + bool hasSingleOrNoSuccessor = block.SuccessorsCount <= 1; + + for (int i = 0; i < block.SuccessorsCount; i++) + { + BasicBlock successor = block.GetSuccessor(i); + + int succIndex = successor.Index; + + // If the current node is a split node, then the actual successor node + // (the successor before the split) should be right after it. + if (IsSplitEdgeBlock(successor)) + { + succIndex = successor.GetSuccessor(0).Index; + } + + CopyResolver copyResolver = null; + + foreach (int iIndex in _blockLiveIn[succIndex]) + { + LiveInterval interval = _parentIntervals[iIndex]; + + if (!interval.IsSplit) + { + continue; + } + + int lEnd = _blockRanges[block.Index].End - 1; + int rStart = _blockRanges[succIndex].Start; + + LiveInterval left = interval.GetSplitChild(lEnd); + LiveInterval right = interval.GetSplitChild(rStart); + + if (left != default && right != default && left != right) + { + if (copyResolver == null) + { + copyResolver = new CopyResolver(); + } + + copyResolver.AddSplit(left, right); + } + } + + if (copyResolver == null || !copyResolver.HasCopy) + { + continue; + } + + Operation[] sequence = copyResolver.Sequence(); + + if (hasSingleOrNoSuccessor) + { + foreach (Operation operation in sequence) + { + block.Append(operation); + } + } + else if (successor.Predecessors.Count == 1) + { + successor.Operations.AddFirst(sequence[0]); + + Operation prependNode = sequence[0]; + + for (int index = 1; index < sequence.Length; index++) + { + Operation operation = sequence[index]; + + successor.Operations.AddAfter(prependNode, operation); + + prependNode = operation; + } + } + else + { + // Split the critical edge. + BasicBlock splitBlock = cfg.SplitEdge(block, successor); + + foreach (Operation operation in sequence) + { + splitBlock.Append(operation); + } + } + } + } + } + + private void ReplaceLocalWithRegister(LiveInterval current) + { + Operand register = GetRegister(current); + + foreach (int usePosition in current.UsePositions()) + { + (_, Operation operation) = GetOperationNode(usePosition); + + for (int index = 0; index < operation.SourcesCount; index++) + { + Operand source = operation.GetSource(index); + + if (source == current.Local) + { + operation.SetSource(index, register); + } + else if (source.Kind == OperandKind.Memory) + { + MemoryOperand memOp = source.GetMemory(); + + if (memOp.BaseAddress == current.Local) + { + memOp.BaseAddress = register; + } + + if (memOp.Index == current.Local) + { + memOp.Index = register; + } + } + } + + for (int index = 0; index < operation.DestinationsCount; index++) + { + Operand dest = operation.GetDestination(index); + + if (dest == current.Local) + { + operation.SetDestination(index, register); + } + } + } + } + + private static Operand GetRegister(LiveInterval interval) + { + Debug.Assert(!interval.IsSpilled, "Spilled intervals are not allowed."); + + return Operand.Factory.Register( + interval.Register.Index, + interval.Register.Type, + interval.Local.Type); + } + + private (IntrusiveList, Operation) GetOperationNode(int position) + { + return _operationNodes[position / InstructionGap]; + } + + private void NumberLocals(ControlFlowGraph cfg, int registersCount) + { + _operationNodes = new List<(IntrusiveList, Operation)>(); + _intervals = new List(); + + for (int index = 0; index < registersCount; index++) + { + _intervals.Add(new LiveInterval(new Register(index, RegisterType.Integer))); + _intervals.Add(new LiveInterval(new Register(index, RegisterType.Vector))); + } + + // The "visited" state is stored in the MSB of the local's value. + const ulong VisitedMask = 1ul << 63; + + bool IsVisited(Operand local) + { + return (local.GetValueUnsafe() & VisitedMask) != 0; + } + + void SetVisited(Operand local) + { + local.GetValueUnsafe() |= VisitedMask; + } + + _operationsCount = 0; + + for (int index = cfg.PostOrderBlocks.Length - 1; index >= 0; index--) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + _operationNodes.Add((block.Operations, node)); + + for (int i = 0; i < node.DestinationsCount; i++) + { + Operand dest = node.GetDestination(i); + + if (dest.Kind == OperandKind.LocalVariable && !IsVisited(dest)) + { + dest.NumberLocal(_intervals.Count); + + _intervals.Add(new LiveInterval(dest)); + + SetVisited(dest); + } + } + } + + _operationsCount += block.Operations.Count * InstructionGap; + + if (block.Operations.Count == 0) + { + // Pretend we have a dummy instruction on the empty block. + _operationNodes.Add((default, default)); + + _operationsCount += InstructionGap; + } + } + + _parentIntervals = _intervals.ToArray(); + } + + private void BuildIntervals(ControlFlowGraph cfg, AllocationContext context) + { + _blockRanges = new LiveRange[cfg.Blocks.Count]; + + int mapSize = _intervals.Count; + + BitMap[] blkLiveGen = new BitMap[cfg.Blocks.Count]; + BitMap[] blkLiveKill = new BitMap[cfg.Blocks.Count]; + + // Compute local live sets. + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + BitMap liveGen = new BitMap(Allocators.Default, mapSize); + BitMap liveKill = new BitMap(Allocators.Default, mapSize); + + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + for (int i = 0; i < node.SourcesCount; i++) + { + VisitSource(node.GetSource(i)); + } + + for (int i = 0; i < node.DestinationsCount; i++) + { + VisitDestination(node.GetDestination(i)); + } + + void VisitSource(Operand source) + { + if (IsLocalOrRegister(source.Kind)) + { + int id = GetOperandId(source); + + if (!liveKill.IsSet(id)) + { + liveGen.Set(id); + } + } + else if (source.Kind == OperandKind.Memory) + { + MemoryOperand memOp = source.GetMemory(); + + if (memOp.BaseAddress != default) + { + VisitSource(memOp.BaseAddress); + } + + if (memOp.Index != default) + { + VisitSource(memOp.Index); + } + } + } + + void VisitDestination(Operand dest) + { + liveKill.Set(GetOperandId(dest)); + } + } + + blkLiveGen [block.Index] = liveGen; + blkLiveKill[block.Index] = liveKill; + } + + // Compute global live sets. + BitMap[] blkLiveIn = new BitMap[cfg.Blocks.Count]; + BitMap[] blkLiveOut = new BitMap[cfg.Blocks.Count]; + + for (int index = 0; index < cfg.Blocks.Count; index++) + { + blkLiveIn [index] = new BitMap(Allocators.Default, mapSize); + blkLiveOut[index] = new BitMap(Allocators.Default, mapSize); + } + + bool modified; + + do + { + modified = false; + + for (int index = 0; index < cfg.PostOrderBlocks.Length; index++) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + BitMap liveOut = blkLiveOut[block.Index]; + + for (int i = 0; i < block.SuccessorsCount; i++) + { + BasicBlock succ = block.GetSuccessor(i); + + modified |= liveOut.Set(blkLiveIn[succ.Index]); + } + + BitMap liveIn = blkLiveIn[block.Index]; + + liveIn.Set (liveOut); + liveIn.Clear(blkLiveKill[block.Index]); + liveIn.Set (blkLiveGen [block.Index]); + } + } + while (modified); + + _blockLiveIn = blkLiveIn; + + _blockEdges = new HashSet(); + + // Compute lifetime intervals. + int operationPos = _operationsCount; + + for (int index = 0; index < cfg.PostOrderBlocks.Length; index++) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + // We handle empty blocks by pretending they have a dummy instruction, + // because otherwise the block would have the same start and end position, + // and this is not valid. + int instCount = Math.Max(block.Operations.Count, 1); + + int blockStart = operationPos - instCount * InstructionGap; + int blockEnd = operationPos; + + _blockRanges[block.Index] = new LiveRange(blockStart, blockEnd); + + _blockEdges.Add(blockStart); + + BitMap liveOut = blkLiveOut[block.Index]; + + foreach (int id in liveOut) + { + _intervals[id].AddRange(blockStart, blockEnd); + } + + if (block.Operations.Count == 0) + { + operationPos -= InstructionGap; + + continue; + } + + for (Operation node = block.Operations.Last; node != default; node = node.ListPrevious) + { + operationPos -= InstructionGap; + + for (int i = 0; i < node.DestinationsCount; i++) + { + VisitDestination(node.GetDestination(i)); + } + + for (int i = 0; i < node.SourcesCount; i++) + { + VisitSource(node.GetSource(i)); + } + + if (node.Instruction == Instruction.Call) + { + AddIntervalCallerSavedReg(context.Masks.IntCallerSavedRegisters, operationPos, RegisterType.Integer); + AddIntervalCallerSavedReg(context.Masks.VecCallerSavedRegisters, operationPos, RegisterType.Vector); + } + + void VisitSource(Operand source) + { + if (IsLocalOrRegister(source.Kind)) + { + LiveInterval interval = _intervals[GetOperandId(source)]; + + interval.AddRange(blockStart, operationPos + 1); + interval.AddUsePosition(operationPos); + } + else if (source.Kind == OperandKind.Memory) + { + MemoryOperand memOp = source.GetMemory(); + + if (memOp.BaseAddress != default) + { + VisitSource(memOp.BaseAddress); + } + + if (memOp.Index != default) + { + VisitSource(memOp.Index); + } + } + } + + void VisitDestination(Operand dest) + { + LiveInterval interval = _intervals[GetOperandId(dest)]; + + if (interval.IsFixed) + { + interval.IsFixedAndUsed = true; + } + + interval.SetStart(operationPos + 1); + interval.AddUsePosition(operationPos + 1); + } + } + } + + foreach (LiveInterval interval in _parentIntervals) + { + interval.Reset(); + } + } + + private void AddIntervalCallerSavedReg(int mask, int operationPos, RegisterType regType) + { + while (mask != 0) + { + int regIndex = BitOperations.TrailingZeroCount(mask); + + Register callerSavedReg = new Register(regIndex, regType); + + LiveInterval interval = _intervals[GetRegisterId(callerSavedReg)]; + + interval.AddRange(operationPos + 1, operationPos + InstructionGap); + + mask &= ~(1 << regIndex); + } + } + + private static int GetOperandId(Operand operand) + { + if (operand.Kind == OperandKind.LocalVariable) + { + return operand.GetLocalNumber(); + } + else if (operand.Kind == OperandKind.Register) + { + return GetRegisterId(operand.GetRegister()); + } + else + { + throw new ArgumentException($"Invalid operand kind \"{operand.Kind}\"."); + } + } + + private static int GetRegisterId(Register register) + { + return (register.Index << 1) | (register.Type == RegisterType.Vector ? 1 : 0); + } + + private static bool IsLocalOrRegister(OperandKind kind) + { + return kind == OperandKind.LocalVariable || + kind == OperandKind.Register; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs new file mode 100644 index 00000000..d739ad28 --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs @@ -0,0 +1,396 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + unsafe readonly struct LiveInterval : IComparable + { + public const int NotFound = -1; + + private struct Data + { + public int End; + public int SpillOffset; + + public LiveRange FirstRange; + public LiveRange PrevRange; + public LiveRange CurrRange; + + public LiveInterval Parent; + + public UseList Uses; + public LiveIntervalList Children; + + public Operand Local; + public Register Register; + + public bool IsFixed; + public bool IsFixedAndUsed; + } + + private readonly Data* _data; + + private ref int End => ref _data->End; + private ref LiveRange FirstRange => ref _data->FirstRange; + private ref LiveRange CurrRange => ref _data->CurrRange; + private ref LiveRange PrevRange => ref _data->PrevRange; + private ref LiveInterval Parent => ref _data->Parent; + private ref UseList Uses => ref _data->Uses; + private ref LiveIntervalList Children => ref _data->Children; + + public Operand Local => _data->Local; + public ref Register Register => ref _data->Register; + public ref int SpillOffset => ref _data->SpillOffset; + + public bool IsFixed => _data->IsFixed; + public ref bool IsFixedAndUsed => ref _data->IsFixedAndUsed; + public bool IsEmpty => FirstRange == default; + public bool IsSplit => Children.Count != 0; + public bool IsSpilled => SpillOffset != -1; + + public int UsesCount => Uses.Count; + + public LiveInterval(Operand local = default, LiveInterval parent = default) + { + _data = Allocators.LiveIntervals.Allocate(); + *_data = default; + + _data->IsFixed = false; + _data->Local = local; + + Parent = parent == default ? this : parent; + Uses = new UseList(); + Children = new LiveIntervalList(); + + FirstRange = default; + CurrRange = default; + PrevRange = default; + + SpillOffset = -1; + } + + public LiveInterval(Register register) : this(local: default, parent: default) + { + _data->IsFixed = true; + + Register = register; + } + + public void Reset() + { + PrevRange = default; + CurrRange = FirstRange; + } + + public void Forward(int position) + { + LiveRange prev = PrevRange; + LiveRange curr = CurrRange; + + while (curr != default && curr.Start < position && !curr.Overlaps(position)) + { + prev = curr; + curr = curr.Next; + } + + PrevRange = prev; + CurrRange = curr; + } + + public int GetStart() + { + Debug.Assert(!IsEmpty, "Empty LiveInterval cannot have a start position."); + + return FirstRange.Start; + } + + public void SetStart(int position) + { + if (FirstRange != default) + { + Debug.Assert(position != FirstRange.End); + + FirstRange.Start = position; + } + else + { + FirstRange = new LiveRange(position, position + 1); + End = position + 1; + } + } + + public int GetEnd() + { + Debug.Assert(!IsEmpty, "Empty LiveInterval cannot have an end position."); + + return End; + } + + public void AddRange(int start, int end) + { + Debug.Assert(start < end, $"Invalid range start position {start}, {end}"); + + if (FirstRange != default) + { + // If the new range ends exactly where the first range start, then coalesce together. + if (end == FirstRange.Start) + { + FirstRange.Start = start; + + return; + } + // If the new range is already contained, then coalesce together. + else if (FirstRange.Overlaps(start, end)) + { + FirstRange.Start = Math.Min(FirstRange.Start, start); + FirstRange.End = Math.Max(FirstRange.End, end); + End = Math.Max(End, end); + + Debug.Assert(FirstRange.Next == default || !FirstRange.Overlaps(FirstRange.Next)); + return; + } + } + + FirstRange = new LiveRange(start, end, FirstRange); + End = Math.Max(End, end); + + Debug.Assert(FirstRange.Next == default || !FirstRange.Overlaps(FirstRange.Next)); + } + + public void AddUsePosition(int position) + { + Uses.Add(position); + } + + public bool Overlaps(int position) + { + LiveRange curr = CurrRange; + + while (curr != default && curr.Start <= position) + { + if (curr.Overlaps(position)) + { + return true; + } + + curr = curr.Next; + } + + return false; + } + + public bool Overlaps(LiveInterval other) + { + return GetOverlapPosition(other) != NotFound; + } + + public int GetOverlapPosition(LiveInterval other) + { + LiveRange a = CurrRange; + LiveRange b = other.CurrRange; + + while (a != default) + { + while (b != default && b.Start < a.Start) + { + if (a.Overlaps(b)) + { + return a.Start; + } + + b = b.Next; + } + + if (b == default) + { + break; + } + else if (a.Overlaps(b)) + { + return a.Start; + } + + a = a.Next; + } + + return NotFound; + } + + public ReadOnlySpan SplitChildren() + { + return Parent.Children.Span; + } + + public ReadOnlySpan UsePositions() + { + return Uses.Span; + } + + public int FirstUse() + { + return Uses.FirstUse; + } + + public int NextUseAfter(int position) + { + return Uses.NextUse(position); + } + + public LiveInterval Split(int position) + { + LiveInterval result = new(Local, Parent); + result.End = End; + + LiveRange prev = PrevRange; + LiveRange curr = CurrRange; + + while (curr != default && curr.Start < position && !curr.Overlaps(position)) + { + prev = curr; + curr = curr.Next; + } + + if (curr.Start >= position) + { + prev.Next = default; + + result.FirstRange = curr; + + End = prev.End; + } + else + { + result.FirstRange = new LiveRange(position, curr.End, curr.Next); + + curr.End = position; + curr.Next = default; + + End = curr.End; + } + + result.Uses = Uses.Split(position); + + AddSplitChild(result); + + Debug.Assert(!IsEmpty, "Left interval is empty after split."); + Debug.Assert(!result.IsEmpty, "Right interval is empty after split."); + + // Make sure the iterator in the new split is pointing to the start. + result.Reset(); + + return result; + } + + private void AddSplitChild(LiveInterval child) + { + Debug.Assert(!child.IsEmpty, "Trying to insert an empty interval."); + + Parent.Children.Add(child); + } + + public LiveInterval GetSplitChild(int position) + { + if (Overlaps(position)) + { + return this; + } + + foreach (LiveInterval splitChild in SplitChildren()) + { + if (splitChild.Overlaps(position)) + { + return splitChild; + } + else if (splitChild.GetStart() > position) + { + break; + } + } + + return default; + } + + public bool TrySpillWithSiblingOffset() + { + foreach (LiveInterval splitChild in SplitChildren()) + { + if (splitChild.IsSpilled) + { + Spill(splitChild.SpillOffset); + + return true; + } + } + + return false; + } + + public void Spill(int offset) + { + SpillOffset = offset; + } + + public int CompareTo(LiveInterval interval) + { + if (FirstRange == default || interval.FirstRange == default) + { + return 0; + } + + return GetStart().CompareTo(interval.GetStart()); + } + + public bool Equals(LiveInterval interval) + { + return interval._data == _data; + } + + public override bool Equals(object obj) + { + return obj is LiveInterval interval && Equals(interval); + } + + public static bool operator ==(LiveInterval a, LiveInterval b) + { + return a.Equals(b); + } + + public static bool operator !=(LiveInterval a, LiveInterval b) + { + return !a.Equals(b); + } + + public override int GetHashCode() + { + return HashCode.Combine((IntPtr)_data); + } + + public override string ToString() + { + LiveInterval self = this; + + IEnumerable GetRanges() + { + LiveRange curr = self.CurrRange; + + while (curr != default) + { + if (curr == self.CurrRange) + { + yield return "*" + curr; + } + else + { + yield return curr.ToString(); + } + + curr = curr.Next; + } + } + + return string.Join(", ", GetRanges()); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/LiveIntervalList.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveIntervalList.cs new file mode 100644 index 00000000..06b979ea --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveIntervalList.cs @@ -0,0 +1,40 @@ +using System; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + unsafe struct LiveIntervalList + { + private LiveInterval* _items; + private int _count; + private int _capacity; + + public int Count => _count; + public Span Span => new(_items, _count); + + public void Add(LiveInterval interval) + { + if (_count + 1 > _capacity) + { + var oldSpan = Span; + + _capacity = Math.Max(4, _capacity * 2); + _items = Allocators.References.Allocate((uint)_capacity); + + var newSpan = Span; + + oldSpan.CopyTo(newSpan); + } + + int position = interval.GetStart(); + int i = _count - 1; + + while (i >= 0 && _items[i].GetStart() > position) + { + _items[i + 1] = _items[i--]; + } + + _items[i + 1] = interval; + _count++; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/LiveRange.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveRange.cs new file mode 100644 index 00000000..e38b5190 --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/LiveRange.cs @@ -0,0 +1,74 @@ +using System; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + unsafe readonly struct LiveRange : IEquatable + { + private struct Data + { + public int Start; + public int End; + public LiveRange Next; + } + + private readonly Data* _data; + + public ref int Start => ref _data->Start; + public ref int End => ref _data->End; + public ref LiveRange Next => ref _data->Next; + + public LiveRange(int start, int end, LiveRange next = default) + { + _data = Allocators.LiveRanges.Allocate(); + + Start = start; + End = end; + Next = next; + } + + public bool Overlaps(int start, int end) + { + return Start < end && start < End; + } + + public bool Overlaps(LiveRange range) + { + return Start < range.End && range.Start < End; + } + + public bool Overlaps(int position) + { + return position >= Start && position < End; + } + + public bool Equals(LiveRange range) + { + return range._data == _data; + } + + public override bool Equals(object obj) + { + return obj is LiveRange range && Equals(range); + } + + public static bool operator ==(LiveRange a, LiveRange b) + { + return a.Equals(b); + } + + public static bool operator !=(LiveRange a, LiveRange b) + { + return !a.Equals(b); + } + + public override int GetHashCode() + { + return HashCode.Combine((IntPtr)_data); + } + + public override string ToString() + { + return $"[{Start}, {End})"; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/RegisterMasks.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/RegisterMasks.cs new file mode 100644 index 00000000..bc948f95 --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/RegisterMasks.cs @@ -0,0 +1,50 @@ +using ARMeilleure.IntermediateRepresentation; +using System; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + readonly struct RegisterMasks + { + public int IntAvailableRegisters { get; } + public int VecAvailableRegisters { get; } + public int IntCallerSavedRegisters { get; } + public int VecCallerSavedRegisters { get; } + public int IntCalleeSavedRegisters { get; } + public int VecCalleeSavedRegisters { get; } + public int RegistersCount { get; } + + public RegisterMasks( + int intAvailableRegisters, + int vecAvailableRegisters, + int intCallerSavedRegisters, + int vecCallerSavedRegisters, + int intCalleeSavedRegisters, + int vecCalleeSavedRegisters, + int registersCount) + { + IntAvailableRegisters = intAvailableRegisters; + VecAvailableRegisters = vecAvailableRegisters; + IntCallerSavedRegisters = intCallerSavedRegisters; + VecCallerSavedRegisters = vecCallerSavedRegisters; + IntCalleeSavedRegisters = intCalleeSavedRegisters; + VecCalleeSavedRegisters = vecCalleeSavedRegisters; + RegistersCount = registersCount; + } + + public int GetAvailableRegisters(RegisterType type) + { + if (type == RegisterType.Integer) + { + return IntAvailableRegisters; + } + else if (type == RegisterType.Vector) + { + return VecAvailableRegisters; + } + else + { + throw new ArgumentException($"Invalid register type \"{type}\"."); + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/StackAllocator.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/StackAllocator.cs new file mode 100644 index 00000000..038312fe --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/StackAllocator.cs @@ -0,0 +1,25 @@ +using ARMeilleure.IntermediateRepresentation; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + class StackAllocator + { + private int _offset; + + public int TotalSize => _offset; + + public int Allocate(OperandType type) + { + return Allocate(type.GetSizeInBytes()); + } + + public int Allocate(int sizeInBytes) + { + int offset = _offset; + + _offset += sizeInBytes; + + return offset; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/RegisterAllocators/UseList.cs b/src/ARMeilleure/CodeGen/RegisterAllocators/UseList.cs new file mode 100644 index 00000000..c89f0854 --- /dev/null +++ b/src/ARMeilleure/CodeGen/RegisterAllocators/UseList.cs @@ -0,0 +1,84 @@ +using System; + +namespace ARMeilleure.CodeGen.RegisterAllocators +{ + unsafe struct UseList + { + private int* _items; + private int _capacity; + private int _count; + + public int Count => _count; + public int FirstUse => _count > 0 ? _items[_count - 1] : LiveInterval.NotFound; + public Span Span => new(_items, _count); + + public void Add(int position) + { + if (_count + 1 > _capacity) + { + var oldSpan = Span; + + _capacity = Math.Max(4, _capacity * 2); + _items = Allocators.Default.Allocate((uint)_capacity); + + var newSpan = Span; + + oldSpan.CopyTo(newSpan); + } + + // Use positions are usually inserted in descending order, so inserting in descending order is faster, + // since the number of half exchanges is reduced. + int i = _count - 1; + + while (i >= 0 && _items[i] < position) + { + _items[i + 1] = _items[i--]; + } + + _items[i + 1] = position; + _count++; + } + + public int NextUse(int position) + { + int index = NextUseIndex(position); + + return index != LiveInterval.NotFound ? _items[index] : LiveInterval.NotFound; + } + + public int NextUseIndex(int position) + { + int i = _count - 1; + + if (i == -1 || position > _items[0]) + { + return LiveInterval.NotFound; + } + + while (i >= 0 && _items[i] < position) + { + i--; + } + + return i; + } + + public UseList Split(int position) + { + int index = NextUseIndex(position); + + // Since the list is in descending order, the new split list takes the front of the list and the current + // list takes the back of the list. + UseList result = new(); + result._count = index + 1; + result._capacity = result._count; + result._items = _items; + + _count = _count - result._count; + _capacity = _count; + _items = _items + result._count; + + return result; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/Unwinding/UnwindInfo.cs b/src/ARMeilleure/CodeGen/Unwinding/UnwindInfo.cs new file mode 100644 index 00000000..3d0bc21d --- /dev/null +++ b/src/ARMeilleure/CodeGen/Unwinding/UnwindInfo.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.CodeGen.Unwinding +{ + struct UnwindInfo + { + public const int Stride = 4; // Bytes. + + public UnwindPushEntry[] PushEntries { get; } + public int PrologSize { get; } + + public UnwindInfo(UnwindPushEntry[] pushEntries, int prologSize) + { + PushEntries = pushEntries; + PrologSize = prologSize; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/Unwinding/UnwindPseudoOp.cs b/src/ARMeilleure/CodeGen/Unwinding/UnwindPseudoOp.cs new file mode 100644 index 00000000..4a8288a2 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Unwinding/UnwindPseudoOp.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.CodeGen.Unwinding +{ + enum UnwindPseudoOp + { + PushReg = 0, + SetFrame = 1, + AllocStack = 2, + SaveReg = 3, + SaveXmm128 = 4 + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/Unwinding/UnwindPushEntry.cs b/src/ARMeilleure/CodeGen/Unwinding/UnwindPushEntry.cs new file mode 100644 index 00000000..fd8ea402 --- /dev/null +++ b/src/ARMeilleure/CodeGen/Unwinding/UnwindPushEntry.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.CodeGen.Unwinding +{ + struct UnwindPushEntry + { + public const int Stride = 16; // Bytes. + + public UnwindPseudoOp PseudoOp { get; } + public int PrologOffset { get; } + public int RegIndex { get; } + public int StackOffsetOrAllocSize { get; } + + public UnwindPushEntry(UnwindPseudoOp pseudoOp, int prologOffset, int regIndex = -1, int stackOffsetOrAllocSize = -1) + { + PseudoOp = pseudoOp; + PrologOffset = prologOffset; + RegIndex = regIndex; + StackOffsetOrAllocSize = stackOffsetOrAllocSize; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/X86/Assembler.cs b/src/ARMeilleure/CodeGen/X86/Assembler.cs new file mode 100644 index 00000000..67736a31 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/Assembler.cs @@ -0,0 +1,1559 @@ +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.IntermediateRepresentation; +using Ryujinx.Common.Memory; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; + +namespace ARMeilleure.CodeGen.X86 +{ + partial class Assembler + { + private const int ReservedBytesForJump = 1; + + private const int OpModRMBits = 24; + + private const byte RexPrefix = 0x40; + private const byte RexWPrefix = 0x48; + private const byte LockPrefix = 0xf0; + + private const int MaxRegNumber = 15; + + private struct Jump + { + public bool IsConditional { get; } + public X86Condition Condition { get; } + public Operand JumpLabel { get; } + public long? JumpTarget { get; set; } + public long JumpPosition { get; } + public long Offset { get; set; } + public int InstSize { get; set; } + + public Jump(Operand jumpLabel, long jumpPosition) + { + IsConditional = false; + Condition = 0; + JumpLabel = jumpLabel; + JumpTarget = null; + JumpPosition = jumpPosition; + + Offset = 0; + InstSize = 0; + } + + public Jump(X86Condition condition, Operand jumpLabel, long jumpPosition) + { + IsConditional = true; + Condition = condition; + JumpLabel = jumpLabel; + JumpTarget = null; + JumpPosition = jumpPosition; + + Offset = 0; + InstSize = 0; + } + } + + private struct Reloc + { + public int JumpIndex { get; set; } + public int Position { get; set; } + public Symbol Symbol { get; set; } + } + + private readonly List _jumps; + private readonly List _relocs; + private readonly Dictionary _labels; + private readonly Stream _stream; + + public bool HasRelocs => _relocs != null; + + public Assembler(Stream stream, bool relocatable) + { + _stream = stream; + _labels = new Dictionary(); + _jumps = new List(); + + _relocs = relocatable ? new List() : null; + } + + public void MarkLabel(Operand label) + { + _labels.Add(label, _stream.Position); + } + + public void Add(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Add); + } + + public void Addsd(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Addsd); + } + + public void Addss(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Addss); + } + + public void And(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.And); + } + + public void Bsr(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Bsr); + } + + public void Bswap(Operand dest) + { + WriteInstruction(dest, default, dest.Type, X86Instruction.Bswap); + } + + public void Call(Operand dest) + { + WriteInstruction(dest, default, OperandType.None, X86Instruction.Call); + } + + public void Cdq() + { + WriteByte(0x99); + } + + public void Cmovcc(Operand dest, Operand source, OperandType type, X86Condition condition) + { + ref readonly InstructionInfo info = ref _instTable[(int)X86Instruction.Cmovcc]; + + WriteOpCode(dest, default, source, type, info.Flags, info.OpRRM | (int)condition, rrm: true); + } + + public void Cmp(Operand src1, Operand src2, OperandType type) + { + WriteInstruction(src1, src2, type, X86Instruction.Cmp); + } + + public void Cqo() + { + WriteByte(0x48); + WriteByte(0x99); + } + + public void Cmpxchg(Operand memOp, Operand src) + { + Debug.Assert(memOp.Kind == OperandKind.Memory); + + WriteByte(LockPrefix); + + WriteInstruction(memOp, src, src.Type, X86Instruction.Cmpxchg); + } + + public void Cmpxchg16(Operand memOp, Operand src) + { + Debug.Assert(memOp.Kind == OperandKind.Memory); + + WriteByte(LockPrefix); + WriteByte(0x66); + + WriteInstruction(memOp, src, src.Type, X86Instruction.Cmpxchg); + } + + public void Cmpxchg16b(Operand memOp) + { + Debug.Assert(memOp.Kind == OperandKind.Memory); + + WriteByte(LockPrefix); + + WriteInstruction(memOp, default, OperandType.None, X86Instruction.Cmpxchg16b); + } + + public void Cmpxchg8(Operand memOp, Operand src) + { + Debug.Assert(memOp.Kind == OperandKind.Memory); + + WriteByte(LockPrefix); + + WriteInstruction(memOp, src, src.Type, X86Instruction.Cmpxchg8); + } + + public void Comisd(Operand src1, Operand src2) + { + WriteInstruction(src1, default, src2, X86Instruction.Comisd); + } + + public void Comiss(Operand src1, Operand src2) + { + WriteInstruction(src1, default, src2, X86Instruction.Comiss); + } + + public void Cvtsd2ss(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Cvtsd2ss); + } + + public void Cvtsi2sd(Operand dest, Operand src1, Operand src2, OperandType type) + { + WriteInstruction(dest, src1, src2, X86Instruction.Cvtsi2sd, type); + } + + public void Cvtsi2ss(Operand dest, Operand src1, Operand src2, OperandType type) + { + WriteInstruction(dest, src1, src2, X86Instruction.Cvtsi2ss, type); + } + + public void Cvtss2sd(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Cvtss2sd); + } + + public void Div(Operand source) + { + WriteInstruction(default, source, source.Type, X86Instruction.Div); + } + + public void Divsd(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Divsd); + } + + public void Divss(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Divss); + } + + public void Idiv(Operand source) + { + WriteInstruction(default, source, source.Type, X86Instruction.Idiv); + } + + public void Imul(Operand source) + { + WriteInstruction(default, source, source.Type, X86Instruction.Imul128); + } + + public void Imul(Operand dest, Operand source, OperandType type) + { + if (source.Kind != OperandKind.Register) + { + throw new ArgumentException($"Invalid source operand kind \"{source.Kind}\"."); + } + + WriteInstruction(dest, source, type, X86Instruction.Imul); + } + + public void Imul(Operand dest, Operand src1, Operand src2, OperandType type) + { + ref readonly InstructionInfo info = ref _instTable[(int)X86Instruction.Imul]; + + if (src2.Kind != OperandKind.Constant) + { + throw new ArgumentException($"Invalid source 2 operand kind \"{src2.Kind}\"."); + } + + if (IsImm8(src2.Value, src2.Type) && info.OpRMImm8 != BadOp) + { + WriteOpCode(dest, default, src1, type, info.Flags, info.OpRMImm8, rrm: true); + + WriteByte(src2.AsByte()); + } + else if (IsImm32(src2.Value, src2.Type) && info.OpRMImm32 != BadOp) + { + WriteOpCode(dest, default, src1, type, info.Flags, info.OpRMImm32, rrm: true); + + WriteInt32(src2.AsInt32()); + } + else + { + throw new ArgumentException($"Failed to encode constant 0x{src2.Value:X}."); + } + } + + public void Insertps(Operand dest, Operand src1, Operand src2, byte imm) + { + WriteInstruction(dest, src1, src2, X86Instruction.Insertps); + + WriteByte(imm); + } + + public void Jcc(X86Condition condition, Operand dest) + { + if (dest.Kind == OperandKind.Label) + { + _jumps.Add(new Jump(condition, dest, _stream.Position)); + + // ReservedBytesForJump + WriteByte(0); + } + else + { + throw new ArgumentException("Destination operand must be of kind Label", nameof(dest)); + } + } + + public void Jcc(X86Condition condition, long offset) + { + if (ConstFitsOnS8(offset)) + { + WriteByte((byte)(0x70 | (int)condition)); + + WriteByte((byte)offset); + } + else if (ConstFitsOnS32(offset)) + { + WriteByte(0x0f); + WriteByte((byte)(0x80 | (int)condition)); + + WriteInt32((int)offset); + } + else + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + } + + public void Jmp(long offset) + { + if (ConstFitsOnS8(offset)) + { + WriteByte(0xeb); + + WriteByte((byte)offset); + } + else if (ConstFitsOnS32(offset)) + { + WriteByte(0xe9); + + WriteInt32((int)offset); + } + else + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + } + + public void Jmp(Operand dest) + { + if (dest.Kind == OperandKind.Label) + { + _jumps.Add(new Jump(dest, _stream.Position)); + + // ReservedBytesForJump + WriteByte(0); + } + else + { + WriteInstruction(dest, default, OperandType.None, X86Instruction.Jmp); + } + } + + public void Ldmxcsr(Operand dest) + { + WriteInstruction(dest, default, OperandType.I32, X86Instruction.Ldmxcsr); + } + + public void Lea(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Lea); + } + + public void LockOr(Operand dest, Operand source, OperandType type) + { + WriteByte(LockPrefix); + WriteInstruction(dest, source, type, X86Instruction.Or); + } + + public void Mov(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Mov); + } + + public void Mov16(Operand dest, Operand source) + { + WriteInstruction(dest, source, OperandType.None, X86Instruction.Mov16); + } + + public void Mov8(Operand dest, Operand source) + { + WriteInstruction(dest, source, OperandType.None, X86Instruction.Mov8); + } + + public void Movd(Operand dest, Operand source) + { + ref readonly InstructionInfo info = ref _instTable[(int)X86Instruction.Movd]; + + if (source.Type.IsInteger() || source.Kind == OperandKind.Memory) + { + WriteOpCode(dest, default, source, OperandType.None, info.Flags, info.OpRRM, rrm: true); + } + else + { + WriteOpCode(dest, default, source, OperandType.None, info.Flags, info.OpRMR); + } + } + + public void Movdqu(Operand dest, Operand source) + { + WriteInstruction(dest, default, source, X86Instruction.Movdqu); + } + + public void Movhlps(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Movhlps); + } + + public void Movlhps(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Movlhps); + } + + public void Movq(Operand dest, Operand source) + { + ref readonly InstructionInfo info = ref _instTable[(int)X86Instruction.Movd]; + + InstructionFlags flags = info.Flags | InstructionFlags.RexW; + + if (source.Type.IsInteger() || source.Kind == OperandKind.Memory) + { + WriteOpCode(dest, default, source, OperandType.None, flags, info.OpRRM, rrm: true); + } + else if (dest.Type.IsInteger() || dest.Kind == OperandKind.Memory) + { + WriteOpCode(dest, default, source, OperandType.None, flags, info.OpRMR); + } + else + { + WriteInstruction(dest, source, OperandType.None, X86Instruction.Movq); + } + } + + public void Movsd(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Movsd); + } + + public void Movss(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Movss); + } + + public void Movsx16(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Movsx16); + } + + public void Movsx32(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Movsx32); + } + + public void Movsx8(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Movsx8); + } + + public void Movzx16(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Movzx16); + } + + public void Movzx8(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Movzx8); + } + + public void Mul(Operand source) + { + WriteInstruction(default, source, source.Type, X86Instruction.Mul128); + } + + public void Mulsd(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Mulsd); + } + + public void Mulss(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Mulss); + } + + public void Neg(Operand dest) + { + WriteInstruction(dest, default, dest.Type, X86Instruction.Neg); + } + + public void Not(Operand dest) + { + WriteInstruction(dest, default, dest.Type, X86Instruction.Not); + } + + public void Or(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Or); + } + + public void Pclmulqdq(Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, default, source, X86Instruction.Pclmulqdq); + + WriteByte(imm); + } + + public void Pcmpeqw(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Pcmpeqw); + } + + public void Pextrb(Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, default, source, X86Instruction.Pextrb); + + WriteByte(imm); + } + + public void Pextrd(Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, default, source, X86Instruction.Pextrd); + + WriteByte(imm); + } + + public void Pextrq(Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, default, source, X86Instruction.Pextrq); + + WriteByte(imm); + } + + public void Pextrw(Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, default, source, X86Instruction.Pextrw); + + WriteByte(imm); + } + + public void Pinsrb(Operand dest, Operand src1, Operand src2, byte imm) + { + WriteInstruction(dest, src1, src2, X86Instruction.Pinsrb); + + WriteByte(imm); + } + + public void Pinsrd(Operand dest, Operand src1, Operand src2, byte imm) + { + WriteInstruction(dest, src1, src2, X86Instruction.Pinsrd); + + WriteByte(imm); + } + + public void Pinsrq(Operand dest, Operand src1, Operand src2, byte imm) + { + WriteInstruction(dest, src1, src2, X86Instruction.Pinsrq); + + WriteByte(imm); + } + + public void Pinsrw(Operand dest, Operand src1, Operand src2, byte imm) + { + WriteInstruction(dest, src1, src2, X86Instruction.Pinsrw); + + WriteByte(imm); + } + + public void Pop(Operand dest) + { + if (dest.Kind == OperandKind.Register) + { + WriteCompactInst(dest, 0x58); + } + else + { + WriteInstruction(dest, default, dest.Type, X86Instruction.Pop); + } + } + + public void Popcnt(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Popcnt); + } + + public void Pshufd(Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, default, source, X86Instruction.Pshufd); + + WriteByte(imm); + } + + public void Push(Operand source) + { + if (source.Kind == OperandKind.Register) + { + WriteCompactInst(source, 0x50); + } + else + { + WriteInstruction(default, source, source.Type, X86Instruction.Push); + } + } + + public void Return() + { + WriteByte(0xc3); + } + + public void Ror(Operand dest, Operand source, OperandType type) + { + WriteShiftInst(dest, source, type, X86Instruction.Ror); + } + + public void Sar(Operand dest, Operand source, OperandType type) + { + WriteShiftInst(dest, source, type, X86Instruction.Sar); + } + + public void Shl(Operand dest, Operand source, OperandType type) + { + WriteShiftInst(dest, source, type, X86Instruction.Shl); + } + + public void Shr(Operand dest, Operand source, OperandType type) + { + WriteShiftInst(dest, source, type, X86Instruction.Shr); + } + + public void Setcc(Operand dest, X86Condition condition) + { + ref readonly InstructionInfo info = ref _instTable[(int)X86Instruction.Setcc]; + + WriteOpCode(dest, default, default, OperandType.None, info.Flags, info.OpRRM | (int)condition); + } + + public void Stmxcsr(Operand dest) + { + WriteInstruction(dest, default, OperandType.I32, X86Instruction.Stmxcsr); + } + + public void Sub(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Sub); + } + + public void Subsd(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Subsd); + } + + public void Subss(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Subss); + } + + public void Test(Operand src1, Operand src2, OperandType type) + { + WriteInstruction(src1, src2, type, X86Instruction.Test); + } + + public void Xor(Operand dest, Operand source, OperandType type) + { + WriteInstruction(dest, source, type, X86Instruction.Xor); + } + + public void Xorps(Operand dest, Operand src1, Operand src2) + { + WriteInstruction(dest, src1, src2, X86Instruction.Xorps); + } + + public void WriteInstruction( + X86Instruction inst, + Operand dest, + Operand source, + OperandType type = OperandType.None) + { + WriteInstruction(dest, default, source, inst, type); + } + + public void WriteInstruction(X86Instruction inst, Operand dest, Operand src1, Operand src2) + { + if (src2.Kind == OperandKind.Constant) + { + WriteInstruction(src1, dest, src2, inst); + } + else + { + WriteInstruction(dest, src1, src2, inst); + } + } + + public void WriteInstruction( + X86Instruction inst, + Operand dest, + Operand src1, + Operand src2, + OperandType type) + { + WriteInstruction(dest, src1, src2, inst, type); + } + + public void WriteInstruction(X86Instruction inst, Operand dest, Operand source, byte imm) + { + WriteInstruction(dest, default, source, inst); + + WriteByte(imm); + } + + public void WriteInstruction( + X86Instruction inst, + Operand dest, + Operand src1, + Operand src2, + Operand src3) + { + // 3+ operands can only be encoded with the VEX encoding scheme. + Debug.Assert(HardwareCapabilities.SupportsVexEncoding); + + WriteInstruction(dest, src1, src2, inst); + + WriteByte((byte)(src3.AsByte() << 4)); + } + + public void WriteInstruction( + X86Instruction inst, + Operand dest, + Operand src1, + Operand src2, + byte imm) + { + WriteInstruction(dest, src1, src2, inst); + + WriteByte(imm); + } + + private void WriteShiftInst(Operand dest, Operand source, OperandType type, X86Instruction inst) + { + if (source.Kind == OperandKind.Register) + { + X86Register shiftReg = (X86Register)source.GetRegister().Index; + + Debug.Assert(shiftReg == X86Register.Rcx, $"Invalid shift register \"{shiftReg}\"."); + + source = default; + } + else if (source.Kind == OperandKind.Constant) + { + source = Operand.Factory.Const((int)source.Value & (dest.Type == OperandType.I32 ? 0x1f : 0x3f)); + } + + WriteInstruction(dest, source, type, inst); + } + + private void WriteInstruction(Operand dest, Operand source, OperandType type, X86Instruction inst) + { + ref readonly InstructionInfo info = ref _instTable[(int)inst]; + + if (source != default) + { + if (source.Kind == OperandKind.Constant) + { + ulong imm = source.Value; + + if (inst == X86Instruction.Mov8) + { + WriteOpCode(dest, default, default, type, info.Flags, info.OpRMImm8); + + WriteByte((byte)imm); + } + else if (inst == X86Instruction.Mov16) + { + WriteOpCode(dest, default, default, type, info.Flags, info.OpRMImm32); + + WriteInt16((short)imm); + } + else if (IsImm8(imm, type) && info.OpRMImm8 != BadOp) + { + WriteOpCode(dest, default, default, type, info.Flags, info.OpRMImm8); + + WriteByte((byte)imm); + } + else if (!source.Relocatable && IsImm32(imm, type) && info.OpRMImm32 != BadOp) + { + WriteOpCode(dest, default, default, type, info.Flags, info.OpRMImm32); + + WriteInt32((int)imm); + } + else if (dest != default && dest.Kind == OperandKind.Register && info.OpRImm64 != BadOp) + { + int rexPrefix = GetRexPrefix(dest, source, type, rrm: false); + + if (rexPrefix != 0) + { + WriteByte((byte)rexPrefix); + } + + WriteByte((byte)(info.OpRImm64 + (dest.GetRegister().Index & 0b111))); + + if (HasRelocs && source.Relocatable) + { + _relocs.Add(new Reloc + { + JumpIndex = _jumps.Count - 1, + Position = (int)_stream.Position, + Symbol = source.Symbol + }); + } + + WriteUInt64(imm); + } + else + { + throw new ArgumentException($"Failed to encode constant 0x{imm:X}."); + } + } + else if (source.Kind == OperandKind.Register && info.OpRMR != BadOp) + { + WriteOpCode(dest, default, source, type, info.Flags, info.OpRMR); + } + else if (info.OpRRM != BadOp) + { + WriteOpCode(dest, default, source, type, info.Flags, info.OpRRM, rrm: true); + } + else + { + throw new ArgumentException($"Invalid source operand kind \"{source.Kind}\"."); + } + } + else if (info.OpRRM != BadOp) + { + WriteOpCode(dest, default, source, type, info.Flags, info.OpRRM, rrm: true); + } + else if (info.OpRMR != BadOp) + { + WriteOpCode(dest, default, source, type, info.Flags, info.OpRMR); + } + else + { + throw new ArgumentNullException(nameof(source)); + } + } + + private void WriteInstruction( + Operand dest, + Operand src1, + Operand src2, + X86Instruction inst, + OperandType type = OperandType.None) + { + ref readonly InstructionInfo info = ref _instTable[(int)inst]; + + if (src2 != default) + { + if (src2.Kind == OperandKind.Constant) + { + ulong imm = src2.Value; + + if ((byte)imm == imm && info.OpRMImm8 != BadOp) + { + WriteOpCode(dest, src1, default, type, info.Flags, info.OpRMImm8); + + WriteByte((byte)imm); + } + else + { + throw new ArgumentException($"Failed to encode constant 0x{imm:X}."); + } + } + else if (src2.Kind == OperandKind.Register && info.OpRMR != BadOp) + { + WriteOpCode(dest, src1, src2, type, info.Flags, info.OpRMR); + } + else if (info.OpRRM != BadOp) + { + WriteOpCode(dest, src1, src2, type, info.Flags, info.OpRRM, rrm: true); + } + else + { + throw new ArgumentException($"Invalid source operand kind \"{src2.Kind}\"."); + } + } + else if (info.OpRRM != BadOp) + { + WriteOpCode(dest, src1, src2, type, info.Flags, info.OpRRM, rrm: true); + } + else if (info.OpRMR != BadOp) + { + WriteOpCode(dest, src1, src2, type, info.Flags, info.OpRMR); + } + else + { + throw new ArgumentNullException(nameof(src2)); + } + } + + private void WriteOpCode( + Operand dest, + Operand src1, + Operand src2, + OperandType type, + InstructionFlags flags, + int opCode, + bool rrm = false) + { + int rexPrefix = GetRexPrefix(dest, src2, type, rrm); + + if ((flags & InstructionFlags.RexW) != 0) + { + rexPrefix |= RexWPrefix; + } + + int modRM = (opCode >> OpModRMBits) << 3; + + MemoryOperand memOp = default; + bool hasMemOp = false; + + if (dest != default) + { + if (dest.Kind == OperandKind.Register) + { + int regIndex = dest.GetRegister().Index; + + modRM |= (regIndex & 0b111) << (rrm ? 3 : 0); + + if ((flags & InstructionFlags.Reg8Dest) != 0 && regIndex >= 4) + { + rexPrefix |= RexPrefix; + } + } + else if (dest.Kind == OperandKind.Memory) + { + memOp = dest.GetMemory(); + hasMemOp = true; + } + else + { + throw new ArgumentException("Invalid destination operand kind \"" + dest.Kind + "\"."); + } + } + + if (src2 != default) + { + if (src2.Kind == OperandKind.Register) + { + int regIndex = src2.GetRegister().Index; + + modRM |= (regIndex & 0b111) << (rrm ? 0 : 3); + + if ((flags & InstructionFlags.Reg8Src) != 0 && regIndex >= 4) + { + rexPrefix |= RexPrefix; + } + } + else if (src2.Kind == OperandKind.Memory && !hasMemOp) + { + memOp = src2.GetMemory(); + hasMemOp = true; + } + else + { + throw new ArgumentException("Invalid source operand kind \"" + src2.Kind + "\"."); + } + } + + bool needsSibByte = false; + bool needsDisplacement = false; + + int sib = 0; + + if (hasMemOp) + { + // Either source or destination is a memory operand. + Register baseReg = memOp.BaseAddress.GetRegister(); + + X86Register baseRegLow = (X86Register)(baseReg.Index & 0b111); + + needsSibByte = memOp.Index != default || baseRegLow == X86Register.Rsp; + needsDisplacement = memOp.Displacement != 0 || baseRegLow == X86Register.Rbp; + + if (needsDisplacement) + { + if (ConstFitsOnS8(memOp.Displacement)) + { + modRM |= 0x40; + } + else /* if (ConstFitsOnS32(memOp.Displacement)) */ + { + modRM |= 0x80; + } + } + + if (baseReg.Index >= 8) + { + Debug.Assert((uint)baseReg.Index <= MaxRegNumber); + + rexPrefix |= RexPrefix | (baseReg.Index >> 3); + } + + if (needsSibByte) + { + sib = (int)baseRegLow; + + if (memOp.Index != default) + { + int indexReg = memOp.Index.GetRegister().Index; + + Debug.Assert(indexReg != (int)X86Register.Rsp, "Using RSP as index register on the memory operand is not allowed."); + + if (indexReg >= 8) + { + Debug.Assert((uint)indexReg <= MaxRegNumber); + + rexPrefix |= RexPrefix | (indexReg >> 3) << 1; + } + + sib |= (indexReg & 0b111) << 3; + } + else + { + sib |= 0b100 << 3; + } + + sib |= (int)memOp.Scale << 6; + + modRM |= 0b100; + } + else + { + modRM |= (int)baseRegLow; + } + } + else + { + // Source and destination are registers. + modRM |= 0xc0; + } + + Debug.Assert(opCode != BadOp, "Invalid opcode value."); + + if ((flags & InstructionFlags.Evex) != 0 && HardwareCapabilities.SupportsEvexEncoding) + { + WriteEvexInst(dest, src1, src2, type, flags, opCode); + + opCode &= 0xff; + } + else if ((flags & InstructionFlags.Vex) != 0 && HardwareCapabilities.SupportsVexEncoding) + { + // In a vex encoding, only one prefix can be active at a time. The active prefix is encoded in the second byte using two bits. + + int vexByte2 = (flags & InstructionFlags.PrefixMask) switch + { + InstructionFlags.Prefix66 => 1, + InstructionFlags.PrefixF3 => 2, + InstructionFlags.PrefixF2 => 3, + _ => 0 + }; + + if (src1 != default) + { + vexByte2 |= (src1.GetRegister().Index ^ 0xf) << 3; + } + else + { + vexByte2 |= 0b1111 << 3; + } + + ushort opCodeHigh = (ushort)(opCode >> 8); + + if ((rexPrefix & 0b1011) == 0 && opCodeHigh == 0xf) + { + // Two-byte form. + WriteByte(0xc5); + + vexByte2 |= (~rexPrefix & 4) << 5; + + WriteByte((byte)vexByte2); + } + else + { + // Three-byte form. + WriteByte(0xc4); + + int vexByte1 = (~rexPrefix & 7) << 5; + + switch (opCodeHigh) + { + case 0xf: vexByte1 |= 1; break; + case 0xf38: vexByte1 |= 2; break; + case 0xf3a: vexByte1 |= 3; break; + + default: Debug.Assert(false, $"Failed to VEX encode opcode 0x{opCode:X}."); break; + } + + vexByte2 |= (rexPrefix & 8) << 4; + + WriteByte((byte)vexByte1); + WriteByte((byte)vexByte2); + } + + opCode &= 0xff; + } + else + { + if (flags.HasFlag(InstructionFlags.Prefix66)) + { + WriteByte(0x66); + } + + if (flags.HasFlag(InstructionFlags.PrefixF2)) + { + WriteByte(0xf2); + } + + if (flags.HasFlag(InstructionFlags.PrefixF3)) + { + WriteByte(0xf3); + } + + if (rexPrefix != 0) + { + WriteByte((byte)rexPrefix); + } + } + + if (dest != default && (flags & InstructionFlags.RegOnly) != 0) + { + opCode += dest.GetRegister().Index & 7; + } + + if ((opCode & 0xff0000) != 0) + { + WriteByte((byte)(opCode >> 16)); + } + + if ((opCode & 0xff00) != 0) + { + WriteByte((byte)(opCode >> 8)); + } + + WriteByte((byte)opCode); + + if ((flags & InstructionFlags.RegOnly) == 0) + { + WriteByte((byte)modRM); + + if (needsSibByte) + { + WriteByte((byte)sib); + } + + if (needsDisplacement) + { + if (ConstFitsOnS8(memOp.Displacement)) + { + WriteByte((byte)memOp.Displacement); + } + else /* if (ConstFitsOnS32(memOp.Displacement)) */ + { + WriteInt32(memOp.Displacement); + } + } + } + } + + private void WriteEvexInst( + Operand dest, + Operand src1, + Operand src2, + OperandType type, + InstructionFlags flags, + int opCode, + bool broadcast = false, + int registerWidth = 128, + int maskRegisterIdx = 0, + bool zeroElements = false) + { + int op1Idx = dest.GetRegister().Index; + int op2Idx = src1.GetRegister().Index; + int op3Idx = src2.GetRegister().Index; + + WriteByte(0x62); + + // P0 + // Extend operand 1 register + bool r = (op1Idx & 8) == 0; + // Extend operand 3 register + bool x = (op3Idx & 16) == 0; + // Extend operand 3 register + bool b = (op3Idx & 8) == 0; + // Extend operand 1 register + bool rp = (op1Idx & 16) == 0; + // Escape code index + byte mm = 0b00; + + switch ((ushort)(opCode >> 8)) + { + case 0xf00: mm = 0b01; break; + case 0xf38: mm = 0b10; break; + case 0xf3a: mm = 0b11; break; + + default: Debug.Fail($"Failed to EVEX encode opcode 0x{opCode:X}."); break; + } + + WriteByte( + (byte)( + (r ? 0x80 : 0) | + (x ? 0x40 : 0) | + (b ? 0x20 : 0) | + (rp ? 0x10 : 0) | + mm)); + + // P1 + // Specify 64-bit lane mode + bool w = Is64Bits(type); + // Operand 2 register index + byte vvvv = (byte)(~op2Idx & 0b1111); + // Opcode prefix + byte pp = (flags & InstructionFlags.PrefixMask) switch + { + InstructionFlags.Prefix66 => 0b01, + InstructionFlags.PrefixF3 => 0b10, + InstructionFlags.PrefixF2 => 0b11, + _ => 0 + }; + WriteByte( + (byte)( + (w ? 0x80 : 0) | + (vvvv << 3) | + 0b100 | + pp)); + + // P2 + // Mask register determines what elements to zero, rather than what elements to merge + bool z = zeroElements; + // Specifies register-width + byte ll = 0b00; + switch (registerWidth) + { + case 128: ll = 0b00; break; + case 256: ll = 0b01; break; + case 512: ll = 0b10; break; + + default: Debug.Fail($"Invalid EVEX vector register width {registerWidth}."); break; + } + // Embedded broadcast in the case of a memory operand + bool bcast = broadcast; + // Extend operand 2 register + bool vp = (op2Idx & 16) == 0; + // Mask register index + Debug.Assert(maskRegisterIdx < 8, $"Invalid mask register index {maskRegisterIdx}."); + byte aaa = (byte)(maskRegisterIdx & 0b111); + + WriteByte( + (byte)( + (z ? 0x80 : 0) | + (ll << 5) | + (bcast ? 0x10 : 0) | + (vp ? 8 : 0) | + aaa)); + } + + private void WriteCompactInst(Operand operand, int opCode) + { + int regIndex = operand.GetRegister().Index; + + if (regIndex >= 8) + { + WriteByte(0x41); + } + + WriteByte((byte)(opCode + (regIndex & 0b111))); + } + + private static int GetRexPrefix(Operand dest, Operand source, OperandType type, bool rrm) + { + int rexPrefix = 0; + + if (Is64Bits(type)) + { + rexPrefix = RexWPrefix; + } + + void SetRegisterHighBit(Register reg, int bit) + { + if (reg.Index >= 8) + { + rexPrefix |= RexPrefix | (reg.Index >> 3) << bit; + } + } + + if (dest != default && dest.Kind == OperandKind.Register) + { + SetRegisterHighBit(dest.GetRegister(), rrm ? 2 : 0); + } + + if (source != default && source.Kind == OperandKind.Register) + { + SetRegisterHighBit(source.GetRegister(), rrm ? 0 : 2); + } + + return rexPrefix; + } + + public (byte[], RelocInfo) GetCode() + { + var jumps = CollectionsMarshal.AsSpan(_jumps); + var relocs = CollectionsMarshal.AsSpan(_relocs); + + // Write jump relative offsets. + bool modified; + + do + { + modified = false; + + for (int i = 0; i < jumps.Length; i++) + { + ref Jump jump = ref jumps[i]; + + // If jump target not resolved yet, resolve it. + if (jump.JumpTarget == null) + { + jump.JumpTarget = _labels[jump.JumpLabel]; + } + + long jumpTarget = jump.JumpTarget.Value; + long offset = jumpTarget - jump.JumpPosition; + + if (offset < 0) + { + for (int j = i - 1; j >= 0; j--) + { + ref Jump jump2 = ref jumps[j]; + + if (jump2.JumpPosition < jumpTarget) + { + break; + } + + offset -= jump2.InstSize - ReservedBytesForJump; + } + } + else + { + for (int j = i + 1; j < jumps.Length; j++) + { + ref Jump jump2 = ref jumps[j]; + + if (jump2.JumpPosition >= jumpTarget) + { + break; + } + + offset += jump2.InstSize - ReservedBytesForJump; + } + + offset -= ReservedBytesForJump; + } + + if (jump.IsConditional) + { + jump.InstSize = GetJccLength(offset); + } + else + { + jump.InstSize = GetJmpLength(offset); + } + + // The jump is relative to the next instruction, not the current one. + // Since we didn't know the next instruction address when calculating + // the offset (as the size of the current jump instruction was not known), + // we now need to compensate the offset with the jump instruction size. + // It's also worth noting that: + // - This is only needed for backward jumps. + // - The GetJmpLength and GetJccLength also compensates the offset + // internally when computing the jump instruction size. + if (offset < 0) + { + offset -= jump.InstSize; + } + + if (jump.Offset != offset) + { + jump.Offset = offset; + + modified = true; + } + } + } + while (modified); + + // Write the code, ignoring the dummy bytes after jumps, into a new stream. + _stream.Seek(0, SeekOrigin.Begin); + + using var codeStream = MemoryStreamManager.Shared.GetStream(); + var assembler = new Assembler(codeStream, HasRelocs); + + bool hasRelocs = HasRelocs; + int relocIndex = 0; + int relocOffset = 0; + var relocEntries = hasRelocs + ? new RelocEntry[relocs.Length] + : Array.Empty(); + + for (int i = 0; i < jumps.Length; i++) + { + ref Jump jump = ref jumps[i]; + + // If has relocations, calculate their new positions compensating for jumps. + if (hasRelocs) + { + relocOffset += jump.InstSize - ReservedBytesForJump; + + for (; relocIndex < relocEntries.Length; relocIndex++) + { + ref Reloc reloc = ref relocs[relocIndex]; + + if (reloc.JumpIndex > i) + { + break; + } + + relocEntries[relocIndex] = new RelocEntry(reloc.Position + relocOffset, reloc.Symbol); + } + } + + Span buffer = new byte[jump.JumpPosition - _stream.Position]; + + _stream.Read(buffer); + _stream.Seek(ReservedBytesForJump, SeekOrigin.Current); + + codeStream.Write(buffer); + + if (jump.IsConditional) + { + assembler.Jcc(jump.Condition, jump.Offset); + } + else + { + assembler.Jmp(jump.Offset); + } + } + + // Write remaining relocations. This case happens when there are no jumps assembled. + for (; relocIndex < relocEntries.Length; relocIndex++) + { + ref Reloc reloc = ref relocs[relocIndex]; + + relocEntries[relocIndex] = new RelocEntry(reloc.Position + relocOffset, reloc.Symbol); + } + + _stream.CopyTo(codeStream); + + var code = codeStream.ToArray(); + var relocInfo = new RelocInfo(relocEntries); + + return (code, relocInfo); + } + + private static bool Is64Bits(OperandType type) + { + return type == OperandType.I64 || type == OperandType.FP64; + } + + private static bool IsImm8(ulong immediate, OperandType type) + { + long value = type == OperandType.I32 ? (int)immediate : (long)immediate; + + return ConstFitsOnS8(value); + } + + private static bool IsImm32(ulong immediate, OperandType type) + { + long value = type == OperandType.I32 ? (int)immediate : (long)immediate; + + return ConstFitsOnS32(value); + } + + private static int GetJccLength(long offset) + { + if (ConstFitsOnS8(offset < 0 ? offset - 2 : offset)) + { + return 2; + } + else if (ConstFitsOnS32(offset < 0 ? offset - 6 : offset)) + { + return 6; + } + else + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + } + + private static int GetJmpLength(long offset) + { + if (ConstFitsOnS8(offset < 0 ? offset - 2 : offset)) + { + return 2; + } + else if (ConstFitsOnS32(offset < 0 ? offset - 5 : offset)) + { + return 5; + } + else + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + } + + private static bool ConstFitsOnS8(long value) + { + return value == (sbyte)value; + } + + private static bool ConstFitsOnS32(long value) + { + return value == (int)value; + } + + private void WriteInt16(short value) + { + WriteUInt16((ushort)value); + } + + private void WriteInt32(int value) + { + WriteUInt32((uint)value); + } + + private void WriteByte(byte value) + { + _stream.WriteByte(value); + } + + private void WriteUInt16(ushort value) + { + _stream.WriteByte((byte)(value >> 0)); + _stream.WriteByte((byte)(value >> 8)); + } + + private void WriteUInt32(uint value) + { + _stream.WriteByte((byte)(value >> 0)); + _stream.WriteByte((byte)(value >> 8)); + _stream.WriteByte((byte)(value >> 16)); + _stream.WriteByte((byte)(value >> 24)); + } + + private void WriteUInt64(ulong value) + { + _stream.WriteByte((byte)(value >> 0)); + _stream.WriteByte((byte)(value >> 8)); + _stream.WriteByte((byte)(value >> 16)); + _stream.WriteByte((byte)(value >> 24)); + _stream.WriteByte((byte)(value >> 32)); + _stream.WriteByte((byte)(value >> 40)); + _stream.WriteByte((byte)(value >> 48)); + _stream.WriteByte((byte)(value >> 56)); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/X86/AssemblerTable.cs b/src/ARMeilleure/CodeGen/X86/AssemblerTable.cs new file mode 100644 index 00000000..e6a2ff07 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/AssemblerTable.cs @@ -0,0 +1,295 @@ +using System; + +namespace ARMeilleure.CodeGen.X86 +{ + partial class Assembler + { + public static bool SupportsVexPrefix(X86Instruction inst) + { + return _instTable[(int)inst].Flags.HasFlag(InstructionFlags.Vex); + } + + private const int BadOp = 0; + + [Flags] + private enum InstructionFlags + { + None = 0, + RegOnly = 1 << 0, + Reg8Src = 1 << 1, + Reg8Dest = 1 << 2, + RexW = 1 << 3, + Vex = 1 << 4, + Evex = 1 << 5, + + PrefixBit = 16, + PrefixMask = 7 << PrefixBit, + Prefix66 = 1 << PrefixBit, + PrefixF3 = 2 << PrefixBit, + PrefixF2 = 4 << PrefixBit + } + + private readonly struct InstructionInfo + { + public int OpRMR { get; } + public int OpRMImm8 { get; } + public int OpRMImm32 { get; } + public int OpRImm64 { get; } + public int OpRRM { get; } + + public InstructionFlags Flags { get; } + + public InstructionInfo( + int opRMR, + int opRMImm8, + int opRMImm32, + int opRImm64, + int opRRM, + InstructionFlags flags) + { + OpRMR = opRMR; + OpRMImm8 = opRMImm8; + OpRMImm32 = opRMImm32; + OpRImm64 = opRImm64; + OpRRM = opRRM; + Flags = flags; + } + } + + private readonly static InstructionInfo[] _instTable; + + static Assembler() + { + _instTable = new InstructionInfo[(int)X86Instruction.Count]; + + // Name RM/R RM/I8 RM/I32 R/I64 R/RM Flags + Add(X86Instruction.Add, new InstructionInfo(0x00000001, 0x00000083, 0x00000081, BadOp, 0x00000003, InstructionFlags.None)); + Add(X86Instruction.Addpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f58, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Addps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f58, InstructionFlags.Vex)); + Add(X86Instruction.Addsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f58, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Addss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f58, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Aesdec, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38de, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Aesdeclast, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38df, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Aesenc, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38dc, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Aesenclast, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38dd, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Aesimc, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38db, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.And, new InstructionInfo(0x00000021, 0x04000083, 0x04000081, BadOp, 0x00000023, InstructionFlags.None)); + Add(X86Instruction.Andnpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f55, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Andnps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f55, InstructionFlags.Vex)); + Add(X86Instruction.Andpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f54, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Andps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f54, InstructionFlags.Vex)); + Add(X86Instruction.Blendvpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3815, InstructionFlags.Prefix66)); + Add(X86Instruction.Blendvps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3814, InstructionFlags.Prefix66)); + Add(X86Instruction.Bsr, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fbd, InstructionFlags.None)); + Add(X86Instruction.Bswap, new InstructionInfo(0x00000fc8, BadOp, BadOp, BadOp, BadOp, InstructionFlags.RegOnly)); + Add(X86Instruction.Call, new InstructionInfo(0x020000ff, BadOp, BadOp, BadOp, BadOp, InstructionFlags.None)); + Add(X86Instruction.Cmovcc, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f40, InstructionFlags.None)); + Add(X86Instruction.Cmp, new InstructionInfo(0x00000039, 0x07000083, 0x07000081, BadOp, 0x0000003b, InstructionFlags.None)); + Add(X86Instruction.Cmppd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc2, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Cmpps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc2, InstructionFlags.Vex)); + Add(X86Instruction.Cmpsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc2, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Cmpss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc2, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Cmpxchg, new InstructionInfo(0x00000fb1, BadOp, BadOp, BadOp, BadOp, InstructionFlags.None)); + Add(X86Instruction.Cmpxchg16b, new InstructionInfo(0x01000fc7, BadOp, BadOp, BadOp, BadOp, InstructionFlags.RexW)); + Add(X86Instruction.Cmpxchg8, new InstructionInfo(0x00000fb0, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Reg8Src)); + Add(X86Instruction.Comisd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2f, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Comiss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2f, InstructionFlags.Vex)); + Add(X86Instruction.Crc32, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38f1, InstructionFlags.PrefixF2)); + Add(X86Instruction.Crc32_16, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38f1, InstructionFlags.PrefixF2 | InstructionFlags.Prefix66)); + Add(X86Instruction.Crc32_8, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38f0, InstructionFlags.PrefixF2 | InstructionFlags.Reg8Src)); + Add(X86Instruction.Cvtdq2pd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fe6, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Cvtdq2ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5b, InstructionFlags.Vex)); + Add(X86Instruction.Cvtpd2dq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fe6, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Cvtpd2ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5a, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Cvtps2dq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5b, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Cvtps2pd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5a, InstructionFlags.Vex)); + Add(X86Instruction.Cvtsd2si, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2d, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Cvtsd2ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5a, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Cvtsi2sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2a, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Cvtsi2ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2a, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Cvtss2sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5a, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Cvtss2si, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f2d, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Div, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x060000f7, InstructionFlags.None)); + Add(X86Instruction.Divpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5e, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Divps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5e, InstructionFlags.Vex)); + Add(X86Instruction.Divsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5e, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Divss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5e, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Gf2p8affineqb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3ace, InstructionFlags.Prefix66)); + Add(X86Instruction.Haddpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f7c, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Haddps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f7c, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Idiv, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x070000f7, InstructionFlags.None)); + Add(X86Instruction.Imul, new InstructionInfo(BadOp, 0x0000006b, 0x00000069, BadOp, 0x00000faf, InstructionFlags.None)); + Add(X86Instruction.Imul128, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x050000f7, InstructionFlags.None)); + Add(X86Instruction.Insertps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a21, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Jmp, new InstructionInfo(0x040000ff, BadOp, BadOp, BadOp, BadOp, InstructionFlags.None)); + Add(X86Instruction.Ldmxcsr, new InstructionInfo(0x02000fae, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Vex)); + Add(X86Instruction.Lea, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x0000008d, InstructionFlags.None)); + Add(X86Instruction.Maxpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5f, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Maxps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5f, InstructionFlags.Vex)); + Add(X86Instruction.Maxsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5f, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Maxss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5f, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Minpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5d, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Minps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5d, InstructionFlags.Vex)); + Add(X86Instruction.Minsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5d, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Minss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5d, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Mov, new InstructionInfo(0x00000089, BadOp, 0x000000c7, 0x000000b8, 0x0000008b, InstructionFlags.None)); + Add(X86Instruction.Mov16, new InstructionInfo(0x00000089, BadOp, 0x000000c7, BadOp, 0x0000008b, InstructionFlags.Prefix66)); + Add(X86Instruction.Mov8, new InstructionInfo(0x00000088, 0x000000c6, BadOp, BadOp, 0x0000008a, InstructionFlags.Reg8Src | InstructionFlags.Reg8Dest)); + Add(X86Instruction.Movd, new InstructionInfo(0x00000f7e, BadOp, BadOp, BadOp, 0x00000f6e, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Movdqu, new InstructionInfo(0x00000f7f, BadOp, BadOp, BadOp, 0x00000f6f, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Movhlps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f12, InstructionFlags.Vex)); + Add(X86Instruction.Movlhps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f16, InstructionFlags.Vex)); + Add(X86Instruction.Movq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f7e, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Movsd, new InstructionInfo(0x00000f11, BadOp, BadOp, BadOp, 0x00000f10, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Movss, new InstructionInfo(0x00000f11, BadOp, BadOp, BadOp, 0x00000f10, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Movsx16, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fbf, InstructionFlags.None)); + Add(X86Instruction.Movsx32, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000063, InstructionFlags.None)); + Add(X86Instruction.Movsx8, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fbe, InstructionFlags.Reg8Src)); + Add(X86Instruction.Movzx16, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fb7, InstructionFlags.None)); + Add(X86Instruction.Movzx8, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fb6, InstructionFlags.Reg8Src)); + Add(X86Instruction.Mul128, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x040000f7, InstructionFlags.None)); + Add(X86Instruction.Mulpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f59, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Mulps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f59, InstructionFlags.Vex)); + Add(X86Instruction.Mulsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f59, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Mulss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f59, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Neg, new InstructionInfo(0x030000f7, BadOp, BadOp, BadOp, BadOp, InstructionFlags.None)); + Add(X86Instruction.Not, new InstructionInfo(0x020000f7, BadOp, BadOp, BadOp, BadOp, InstructionFlags.None)); + Add(X86Instruction.Or, new InstructionInfo(0x00000009, 0x01000083, 0x01000081, BadOp, 0x0000000b, InstructionFlags.None)); + Add(X86Instruction.Paddb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000ffc, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Paddd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000ffe, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Paddq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fd4, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Paddw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000ffd, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Palignr, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a0f, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pand, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fdb, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pandn, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fdf, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pavgb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fe0, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pavgw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fe3, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pblendvb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3810, InstructionFlags.Prefix66)); + Add(X86Instruction.Pclmulqdq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a44, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pcmpeqb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f74, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pcmpeqd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f76, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pcmpeqq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3829, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pcmpeqw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f75, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pcmpgtb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f64, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pcmpgtd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f66, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pcmpgtq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3837, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pcmpgtw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f65, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pextrb, new InstructionInfo(0x000f3a14, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pextrd, new InstructionInfo(0x000f3a16, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pextrq, new InstructionInfo(0x000f3a16, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Vex | InstructionFlags.RexW | InstructionFlags.Prefix66)); + Add(X86Instruction.Pextrw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc5, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pinsrb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a20, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pinsrd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a22, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pinsrq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a22, InstructionFlags.Vex | InstructionFlags.RexW | InstructionFlags.Prefix66)); + Add(X86Instruction.Pinsrw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc4, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmaxsb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f383c, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmaxsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f383d, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmaxsw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fee, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmaxub, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fde, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmaxud, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f383f, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmaxuw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f383e, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pminsb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3838, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pminsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3839, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pminsw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fea, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pminub, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fda, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pminud, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f383b, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pminuw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f383a, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmovsxbw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3820, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmovsxdq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3825, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmovsxwd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3823, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmovzxbw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3830, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmovzxdq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3835, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmovzxwd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3833, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmulld, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3840, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pmullw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fd5, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pop, new InstructionInfo(0x0000008f, BadOp, BadOp, BadOp, BadOp, InstructionFlags.None)); + Add(X86Instruction.Popcnt, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fb8, InstructionFlags.PrefixF3)); + Add(X86Instruction.Por, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000feb, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pshufb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3800, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pshufd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f70, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pslld, new InstructionInfo(BadOp, 0x06000f72, BadOp, BadOp, 0x00000ff2, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Pslldq, new InstructionInfo(BadOp, 0x07000f73, BadOp, BadOp, BadOp, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psllq, new InstructionInfo(BadOp, 0x06000f73, BadOp, BadOp, 0x00000ff3, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psllw, new InstructionInfo(BadOp, 0x06000f71, BadOp, BadOp, 0x00000ff1, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psrad, new InstructionInfo(BadOp, 0x04000f72, BadOp, BadOp, 0x00000fe2, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psraw, new InstructionInfo(BadOp, 0x04000f71, BadOp, BadOp, 0x00000fe1, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psrld, new InstructionInfo(BadOp, 0x02000f72, BadOp, BadOp, 0x00000fd2, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psrlq, new InstructionInfo(BadOp, 0x02000f73, BadOp, BadOp, 0x00000fd3, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psrldq, new InstructionInfo(BadOp, 0x03000f73, BadOp, BadOp, BadOp, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psrlw, new InstructionInfo(BadOp, 0x02000f71, BadOp, BadOp, 0x00000fd1, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psubb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000ff8, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psubd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000ffa, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psubq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000ffb, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Psubw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000ff9, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Punpckhbw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f68, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Punpckhdq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f6a, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Punpckhqdq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f6d, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Punpckhwd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f69, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Punpcklbw, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f60, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Punpckldq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f62, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Punpcklqdq, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f6c, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Punpcklwd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f61, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Push, new InstructionInfo(BadOp, 0x0000006a, 0x00000068, BadOp, 0x060000ff, InstructionFlags.None)); + Add(X86Instruction.Pxor, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fef, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Rcpps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f53, InstructionFlags.Vex)); + Add(X86Instruction.Rcpss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f53, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Ror, new InstructionInfo(0x010000d3, 0x010000c1, BadOp, BadOp, BadOp, InstructionFlags.None)); + Add(X86Instruction.Roundpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a09, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Roundps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a08, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Roundsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a0b, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Roundss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a0a, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Rsqrtps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f52, InstructionFlags.Vex)); + Add(X86Instruction.Rsqrtss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f52, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Sar, new InstructionInfo(0x070000d3, 0x070000c1, BadOp, BadOp, BadOp, InstructionFlags.None)); + Add(X86Instruction.Setcc, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f90, InstructionFlags.Reg8Dest)); + Add(X86Instruction.Sha256Msg1, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38cc, InstructionFlags.None)); + Add(X86Instruction.Sha256Msg2, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38cd, InstructionFlags.None)); + Add(X86Instruction.Sha256Rnds2, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38cb, InstructionFlags.None)); + Add(X86Instruction.Shl, new InstructionInfo(0x040000d3, 0x040000c1, BadOp, BadOp, BadOp, InstructionFlags.None)); + Add(X86Instruction.Shr, new InstructionInfo(0x050000d3, 0x050000c1, BadOp, BadOp, BadOp, InstructionFlags.None)); + Add(X86Instruction.Shufpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc6, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Shufps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000fc6, InstructionFlags.Vex)); + Add(X86Instruction.Sqrtpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f51, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Sqrtps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f51, InstructionFlags.Vex)); + Add(X86Instruction.Sqrtsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f51, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Sqrtss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f51, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Stmxcsr, new InstructionInfo(0x03000fae, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Vex)); + Add(X86Instruction.Sub, new InstructionInfo(0x00000029, 0x05000083, 0x05000081, BadOp, 0x0000002b, InstructionFlags.None)); + Add(X86Instruction.Subpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5c, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Subps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5c, InstructionFlags.Vex)); + Add(X86Instruction.Subsd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5c, InstructionFlags.Vex | InstructionFlags.PrefixF2)); + Add(X86Instruction.Subss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f5c, InstructionFlags.Vex | InstructionFlags.PrefixF3)); + Add(X86Instruction.Test, new InstructionInfo(0x00000085, BadOp, 0x000000f7, BadOp, BadOp, InstructionFlags.None)); + Add(X86Instruction.Unpckhpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f15, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Unpckhps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f15, InstructionFlags.Vex)); + Add(X86Instruction.Unpcklpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f14, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Unpcklps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f14, InstructionFlags.Vex)); + Add(X86Instruction.Vblendvpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4b, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Vblendvps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4a, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Vcvtph2ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3813, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Vcvtps2ph, new InstructionInfo(0x000f3a1d, BadOp, BadOp, BadOp, BadOp, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Vfmadd231pd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b8, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW)); + Add(X86Instruction.Vfmadd231ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b8, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Vfmadd231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b9, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW)); + Add(X86Instruction.Vfmadd231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38b9, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Vfmsub231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bb, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW)); + Add(X86Instruction.Vfmsub231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bb, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Vfnmadd231pd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bc, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW)); + Add(X86Instruction.Vfnmadd231ps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bc, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Vfnmadd231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bd, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW)); + Add(X86Instruction.Vfnmadd231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bd, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Vfnmsub231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW)); + Add(X86Instruction.Vfnmsub231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Vpblendvb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4c, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Vpternlogd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a25, InstructionFlags.Evex | InstructionFlags.Prefix66)); + Add(X86Instruction.Xor, new InstructionInfo(0x00000031, 0x06000083, 0x06000081, BadOp, 0x00000033, InstructionFlags.None)); + Add(X86Instruction.Xorpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex | InstructionFlags.Prefix66)); + Add(X86Instruction.Xorps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex)); + + static void Add(X86Instruction inst, in InstructionInfo info) + { + _instTable[(int)inst] = info; + } + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/CallConvName.cs b/src/ARMeilleure/CodeGen/X86/CallConvName.cs new file mode 100644 index 00000000..be367628 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/CallConvName.cs @@ -0,0 +1,8 @@ +namespace ARMeilleure.CodeGen.X86 +{ + enum CallConvName + { + SystemV, + Windows + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/X86/CallingConvention.cs b/src/ARMeilleure/CodeGen/X86/CallingConvention.cs new file mode 100644 index 00000000..953fef5b --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/CallingConvention.cs @@ -0,0 +1,158 @@ +using System; + +namespace ARMeilleure.CodeGen.X86 +{ + static class CallingConvention + { + private const int RegistersMask = 0xffff; + + public static int GetIntAvailableRegisters() + { + return RegistersMask & ~(1 << (int)X86Register.Rsp); + } + + public static int GetVecAvailableRegisters() + { + return RegistersMask; + } + + public static int GetIntCallerSavedRegisters() + { + if (GetCurrentCallConv() == CallConvName.Windows) + { + return (1 << (int)X86Register.Rax) | + (1 << (int)X86Register.Rcx) | + (1 << (int)X86Register.Rdx) | + (1 << (int)X86Register.R8) | + (1 << (int)X86Register.R9) | + (1 << (int)X86Register.R10) | + (1 << (int)X86Register.R11); + } + else /* if (GetCurrentCallConv() == CallConvName.SystemV) */ + { + return (1 << (int)X86Register.Rax) | + (1 << (int)X86Register.Rcx) | + (1 << (int)X86Register.Rdx) | + (1 << (int)X86Register.Rsi) | + (1 << (int)X86Register.Rdi) | + (1 << (int)X86Register.R8) | + (1 << (int)X86Register.R9) | + (1 << (int)X86Register.R10) | + (1 << (int)X86Register.R11); + } + } + + public static int GetVecCallerSavedRegisters() + { + if (GetCurrentCallConv() == CallConvName.Windows) + { + return (1 << (int)X86Register.Xmm0) | + (1 << (int)X86Register.Xmm1) | + (1 << (int)X86Register.Xmm2) | + (1 << (int)X86Register.Xmm3) | + (1 << (int)X86Register.Xmm4) | + (1 << (int)X86Register.Xmm5); + } + else /* if (GetCurrentCallConv() == CallConvName.SystemV) */ + { + return RegistersMask; + } + } + + public static int GetIntCalleeSavedRegisters() + { + return GetIntCallerSavedRegisters() ^ RegistersMask; + } + + public static int GetVecCalleeSavedRegisters() + { + return GetVecCallerSavedRegisters() ^ RegistersMask; + } + + public static int GetArgumentsOnRegsCount() + { + return 4; + } + + public static int GetIntArgumentsOnRegsCount() + { + return 6; + } + + public static int GetVecArgumentsOnRegsCount() + { + return 8; + } + + public static X86Register GetIntArgumentRegister(int index) + { + if (GetCurrentCallConv() == CallConvName.Windows) + { + switch (index) + { + case 0: return X86Register.Rcx; + case 1: return X86Register.Rdx; + case 2: return X86Register.R8; + case 3: return X86Register.R9; + } + } + else /* if (GetCurrentCallConv() == CallConvName.SystemV) */ + { + switch (index) + { + case 0: return X86Register.Rdi; + case 1: return X86Register.Rsi; + case 2: return X86Register.Rdx; + case 3: return X86Register.Rcx; + case 4: return X86Register.R8; + case 5: return X86Register.R9; + } + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + + public static X86Register GetVecArgumentRegister(int index) + { + int count; + + if (GetCurrentCallConv() == CallConvName.Windows) + { + count = 4; + } + else /* if (GetCurrentCallConv() == CallConvName.SystemV) */ + { + count = 8; + } + + if ((uint)index < count) + { + return X86Register.Xmm0 + index; + } + + throw new ArgumentOutOfRangeException(nameof(index)); + } + + public static X86Register GetIntReturnRegister() + { + return X86Register.Rax; + } + + public static X86Register GetIntReturnRegisterHigh() + { + return X86Register.Rdx; + } + + public static X86Register GetVecReturnRegister() + { + return X86Register.Xmm0; + } + + public static CallConvName GetCurrentCallConv() + { + return OperatingSystem.IsWindows() + ? CallConvName.Windows + : CallConvName.SystemV; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/X86/CodeGenCommon.cs b/src/ARMeilleure/CodeGen/X86/CodeGenCommon.cs new file mode 100644 index 00000000..237ecee4 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/CodeGenCommon.cs @@ -0,0 +1,19 @@ +using ARMeilleure.IntermediateRepresentation; + +namespace ARMeilleure.CodeGen.X86 +{ + static class CodeGenCommon + { + public static bool IsLongConst(Operand op) + { + long value = op.Type == OperandType.I32 ? op.AsInt32() : op.AsInt64(); + + return !ConstFitsOnS32(value); + } + + private static bool ConstFitsOnS32(long value) + { + return value == (int)value; + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/CodeGenContext.cs b/src/ARMeilleure/CodeGen/X86/CodeGenContext.cs new file mode 100644 index 00000000..89948724 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/CodeGenContext.cs @@ -0,0 +1,105 @@ +using ARMeilleure.CodeGen.RegisterAllocators; +using ARMeilleure.IntermediateRepresentation; +using Ryujinx.Common.Memory; +using System.IO; +using System.Numerics; + +namespace ARMeilleure.CodeGen.X86 +{ + class CodeGenContext + { + private readonly Stream _stream; + private readonly Operand[] _blockLabels; + + public int StreamOffset => (int)_stream.Length; + + public AllocationResult AllocResult { get; } + + public Assembler Assembler { get; } + public BasicBlock CurrBlock { get; private set; } + + public int CallArgsRegionSize { get; } + public int XmmSaveRegionSize { get; } + + public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable) + { + _stream = MemoryStreamManager.Shared.GetStream(); + _blockLabels = new Operand[blocksCount]; + + AllocResult = allocResult; + Assembler = new Assembler(_stream, relocatable); + + CallArgsRegionSize = GetCallArgsRegionSize(allocResult, maxCallArgs, out int xmmSaveRegionSize); + XmmSaveRegionSize = xmmSaveRegionSize; + } + + private static int GetCallArgsRegionSize(AllocationResult allocResult, int maxCallArgs, out int xmmSaveRegionSize) + { + // We need to add 8 bytes to the total size, as the call to this function already pushed 8 bytes (the + // return address). + int intMask = CallingConvention.GetIntCalleeSavedRegisters() & allocResult.IntUsedRegisters; + int vecMask = CallingConvention.GetVecCalleeSavedRegisters() & allocResult.VecUsedRegisters; + + xmmSaveRegionSize = BitOperations.PopCount((uint)vecMask) * 16; + + int calleeSaveRegionSize = BitOperations.PopCount((uint)intMask) * 8 + xmmSaveRegionSize + 8; + + int argsCount = maxCallArgs; + + if (argsCount < 0) + { + // When the function has no calls, argsCount is -1. In this case, we don't need to allocate the shadow + // space. + argsCount = 0; + } + else if (argsCount < 4) + { + // The ABI mandates that the space for at least 4 arguments is reserved on the stack (this is called + // shadow space). + argsCount = 4; + } + + // TODO: Align XMM save region to 16 bytes because unwinding on Windows requires it. + int frameSize = calleeSaveRegionSize + allocResult.SpillRegionSize; + + // TODO: Instead of always multiplying by 16 (the largest possible size of a variable, since a V128 has 16 + // bytes), we should calculate the exact size consumed by the arguments passed to the called functions on + // the stack. + int callArgsAndFrameSize = frameSize + argsCount * 16; + + // Ensure that the Stack Pointer will be aligned to 16 bytes. + callArgsAndFrameSize = (callArgsAndFrameSize + 0xf) & ~0xf; + + return callArgsAndFrameSize - frameSize; + } + + public void EnterBlock(BasicBlock block) + { + Assembler.MarkLabel(GetLabel(block)); + + CurrBlock = block; + } + + public void JumpTo(BasicBlock target) + { + Assembler.Jmp(GetLabel(target)); + } + + public void JumpTo(X86Condition condition, BasicBlock target) + { + Assembler.Jcc(condition, GetLabel(target)); + } + + private Operand GetLabel(BasicBlock block) + { + ref Operand label = ref _blockLabels[block.Index]; + + if (label == default) + { + label = Operand.Factory.Label(); + } + + return label; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/X86/CodeGenerator.cs b/src/ARMeilleure/CodeGen/X86/CodeGenerator.cs new file mode 100644 index 00000000..e7179b51 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/CodeGenerator.cs @@ -0,0 +1,1865 @@ +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.CodeGen.Optimizations; +using ARMeilleure.CodeGen.RegisterAllocators; +using ARMeilleure.CodeGen.Unwinding; +using ARMeilleure.Common; +using ARMeilleure.Diagnostics; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Numerics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.CodeGen.X86 +{ + static class CodeGenerator + { + private const int RegistersCount = 16; + private const int PageSize = 0x1000; + private const int StackGuardSize = 0x2000; + + private static readonly Action[] _instTable; + + static CodeGenerator() + { + _instTable = new Action[EnumUtils.GetCount(typeof(Instruction))]; + + Add(Instruction.Add, GenerateAdd); + Add(Instruction.BitwiseAnd, GenerateBitwiseAnd); + Add(Instruction.BitwiseExclusiveOr, GenerateBitwiseExclusiveOr); + Add(Instruction.BitwiseNot, GenerateBitwiseNot); + Add(Instruction.BitwiseOr, GenerateBitwiseOr); + Add(Instruction.BranchIf, GenerateBranchIf); + Add(Instruction.ByteSwap, GenerateByteSwap); + Add(Instruction.Call, GenerateCall); + Add(Instruction.Clobber, GenerateClobber); + Add(Instruction.Compare, GenerateCompare); + Add(Instruction.CompareAndSwap, GenerateCompareAndSwap); + Add(Instruction.CompareAndSwap16, GenerateCompareAndSwap16); + Add(Instruction.CompareAndSwap8, GenerateCompareAndSwap8); + Add(Instruction.ConditionalSelect, GenerateConditionalSelect); + Add(Instruction.ConvertI64ToI32, GenerateConvertI64ToI32); + Add(Instruction.ConvertToFP, GenerateConvertToFP); + Add(Instruction.Copy, GenerateCopy); + Add(Instruction.CountLeadingZeros, GenerateCountLeadingZeros); + Add(Instruction.Divide, GenerateDivide); + Add(Instruction.DivideUI, GenerateDivideUI); + Add(Instruction.Fill, GenerateFill); + Add(Instruction.Load, GenerateLoad); + Add(Instruction.Load16, GenerateLoad16); + Add(Instruction.Load8, GenerateLoad8); + Add(Instruction.MemoryBarrier, GenerateMemoryBarrier); + Add(Instruction.Multiply, GenerateMultiply); + Add(Instruction.Multiply64HighSI, GenerateMultiply64HighSI); + Add(Instruction.Multiply64HighUI, GenerateMultiply64HighUI); + Add(Instruction.Negate, GenerateNegate); + Add(Instruction.Return, GenerateReturn); + Add(Instruction.RotateRight, GenerateRotateRight); + Add(Instruction.ShiftLeft, GenerateShiftLeft); + Add(Instruction.ShiftRightSI, GenerateShiftRightSI); + Add(Instruction.ShiftRightUI, GenerateShiftRightUI); + Add(Instruction.SignExtend16, GenerateSignExtend16); + Add(Instruction.SignExtend32, GenerateSignExtend32); + Add(Instruction.SignExtend8, GenerateSignExtend8); + Add(Instruction.Spill, GenerateSpill); + Add(Instruction.SpillArg, GenerateSpillArg); + Add(Instruction.StackAlloc, GenerateStackAlloc); + Add(Instruction.Store, GenerateStore); + Add(Instruction.Store16, GenerateStore16); + Add(Instruction.Store8, GenerateStore8); + Add(Instruction.Subtract, GenerateSubtract); + Add(Instruction.Tailcall, GenerateTailcall); + Add(Instruction.VectorCreateScalar, GenerateVectorCreateScalar); + Add(Instruction.VectorExtract, GenerateVectorExtract); + Add(Instruction.VectorExtract16, GenerateVectorExtract16); + Add(Instruction.VectorExtract8, GenerateVectorExtract8); + Add(Instruction.VectorInsert, GenerateVectorInsert); + Add(Instruction.VectorInsert16, GenerateVectorInsert16); + Add(Instruction.VectorInsert8, GenerateVectorInsert8); + Add(Instruction.VectorOne, GenerateVectorOne); + Add(Instruction.VectorZero, GenerateVectorZero); + Add(Instruction.VectorZeroUpper64, GenerateVectorZeroUpper64); + Add(Instruction.VectorZeroUpper96, GenerateVectorZeroUpper96); + Add(Instruction.ZeroExtend16, GenerateZeroExtend16); + Add(Instruction.ZeroExtend32, GenerateZeroExtend32); + Add(Instruction.ZeroExtend8, GenerateZeroExtend8); + + static void Add(Instruction inst, Action func) + { + _instTable[(int)inst] = func; + } + } + + public static CompiledFunction Generate(CompilerContext cctx) + { + ControlFlowGraph cfg = cctx.Cfg; + + Logger.StartPass(PassName.Optimization); + + if (cctx.Options.HasFlag(CompilerOptions.Optimize)) + { + if (cctx.Options.HasFlag(CompilerOptions.SsaForm)) + { + Optimizer.RunPass(cfg); + } + + BlockPlacement.RunPass(cfg); + } + + X86Optimizer.RunPass(cfg); + + Logger.EndPass(PassName.Optimization, cfg); + + Logger.StartPass(PassName.PreAllocation); + + StackAllocator stackAlloc = new(); + + PreAllocator.RunPass(cctx, stackAlloc, out int maxCallArgs); + + Logger.EndPass(PassName.PreAllocation, cfg); + + Logger.StartPass(PassName.RegisterAllocation); + + if (cctx.Options.HasFlag(CompilerOptions.SsaForm)) + { + Ssa.Deconstruct(cfg); + } + + IRegisterAllocator regAlloc; + + if (cctx.Options.HasFlag(CompilerOptions.Lsra)) + { + regAlloc = new LinearScanAllocator(); + } + else + { + regAlloc = new HybridAllocator(); + } + + RegisterMasks regMasks = new( + CallingConvention.GetIntAvailableRegisters(), + CallingConvention.GetVecAvailableRegisters(), + CallingConvention.GetIntCallerSavedRegisters(), + CallingConvention.GetVecCallerSavedRegisters(), + CallingConvention.GetIntCalleeSavedRegisters(), + CallingConvention.GetVecCalleeSavedRegisters(), + RegistersCount); + + AllocationResult allocResult = regAlloc.RunPass(cfg, stackAlloc, regMasks); + + Logger.EndPass(PassName.RegisterAllocation, cfg); + + Logger.StartPass(PassName.CodeGeneration); + + bool relocatable = (cctx.Options & CompilerOptions.Relocatable) != 0; + + CodeGenContext context = new(allocResult, maxCallArgs, cfg.Blocks.Count, relocatable); + + UnwindInfo unwindInfo = WritePrologue(context); + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + context.EnterBlock(block); + + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + GenerateOperation(context, node); + } + + if (block.SuccessorsCount == 0) + { + // The only blocks which can have 0 successors are exit blocks. + Operation last = block.Operations.Last; + + Debug.Assert(last.Instruction == Instruction.Tailcall || + last.Instruction == Instruction.Return); + } + else + { + BasicBlock succ = block.GetSuccessor(0); + + if (succ != block.ListNext) + { + context.JumpTo(succ); + } + } + } + + (byte[] code, RelocInfo relocInfo) = context.Assembler.GetCode(); + + Logger.EndPass(PassName.CodeGeneration); + + return new CompiledFunction(code, unwindInfo, relocInfo); + } + + private static void GenerateOperation(CodeGenContext context, Operation operation) + { + if (operation.Instruction == Instruction.Extended) + { + IntrinsicInfo info = IntrinsicTable.GetInfo(operation.Intrinsic); + + switch (info.Type) + { + case IntrinsicType.Comis_: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + switch (operation.Intrinsic) + { + case Intrinsic.X86Comisdeq: + context.Assembler.Comisd(src1, src2); + context.Assembler.Setcc(dest, X86Condition.Equal); + break; + + case Intrinsic.X86Comisdge: + context.Assembler.Comisd(src1, src2); + context.Assembler.Setcc(dest, X86Condition.AboveOrEqual); + break; + + case Intrinsic.X86Comisdlt: + context.Assembler.Comisd(src1, src2); + context.Assembler.Setcc(dest, X86Condition.Below); + break; + + case Intrinsic.X86Comisseq: + context.Assembler.Comiss(src1, src2); + context.Assembler.Setcc(dest, X86Condition.Equal); + break; + + case Intrinsic.X86Comissge: + context.Assembler.Comiss(src1, src2); + context.Assembler.Setcc(dest, X86Condition.AboveOrEqual); + break; + + case Intrinsic.X86Comisslt: + context.Assembler.Comiss(src1, src2); + context.Assembler.Setcc(dest, X86Condition.Below); + break; + } + + context.Assembler.Movzx8(dest, dest, OperandType.I32); + + break; + } + + case IntrinsicType.Mxcsr: + { + Operand offset = operation.GetSource(0); + + Debug.Assert(offset.Kind == OperandKind.Constant); + Debug.Assert(offset.Type == OperandType.I32); + + int offs = offset.AsInt32() + context.CallArgsRegionSize; + + Operand rsp = Register(X86Register.Rsp); + Operand memOp = MemoryOp(OperandType.I32, rsp, default, Multiplier.x1, offs); + + Debug.Assert(HardwareCapabilities.SupportsSse || HardwareCapabilities.SupportsVexEncoding); + + if (operation.Intrinsic == Intrinsic.X86Ldmxcsr) + { + Operand bits = operation.GetSource(1); + Debug.Assert(bits.Type == OperandType.I32); + + context.Assembler.Mov(memOp, bits, OperandType.I32); + context.Assembler.Ldmxcsr(memOp); + } + else if (operation.Intrinsic == Intrinsic.X86Stmxcsr) + { + Operand dest = operation.Destination; + Debug.Assert(dest.Type == OperandType.I32); + + context.Assembler.Stmxcsr(memOp); + context.Assembler.Mov(dest, memOp, OperandType.I32); + } + + break; + } + + case IntrinsicType.PopCount: + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + EnsureSameType(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Popcnt(dest, source, dest.Type); + + break; + } + + case IntrinsicType.Unary: + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + EnsureSameType(dest, source); + + Debug.Assert(!dest.Type.IsInteger()); + + context.Assembler.WriteInstruction(info.Inst, dest, source); + + break; + } + + case IntrinsicType.UnaryToGpr: + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && !source.Type.IsInteger()); + + if (operation.Intrinsic == Intrinsic.X86Cvtsi2si) + { + if (dest.Type == OperandType.I32) + { + context.Assembler.Movd(dest, source); // int _mm_cvtsi128_si32(__m128i a) + } + else /* if (dest.Type == OperandType.I64) */ + { + context.Assembler.Movq(dest, source); // __int64 _mm_cvtsi128_si64(__m128i a) + } + } + else + { + context.Assembler.WriteInstruction(info.Inst, dest, source, dest.Type); + } + + break; + } + + case IntrinsicType.Binary: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(dest, src1); + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(!dest.Type.IsInteger()); + Debug.Assert(!src2.Type.IsInteger() || src2.Kind == OperandKind.Constant); + + context.Assembler.WriteInstruction(info.Inst, dest, src1, src2); + + break; + } + + case IntrinsicType.BinaryGpr: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(dest, src1); + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(!dest.Type.IsInteger() && src2.Type.IsInteger()); + + context.Assembler.WriteInstruction(info.Inst, dest, src1, src2, src2.Type); + + break; + } + + case IntrinsicType.Crc32: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameReg(dest, src1); + + Debug.Assert(dest.Type.IsInteger() && src1.Type.IsInteger() && src2.Type.IsInteger()); + + context.Assembler.WriteInstruction(info.Inst, dest, src2, dest.Type); + + break; + } + + case IntrinsicType.BinaryImm: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(dest, src1); + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(!dest.Type.IsInteger() && src2.Kind == OperandKind.Constant); + + context.Assembler.WriteInstruction(info.Inst, dest, src1, src2.AsByte()); + + break; + } + + case IntrinsicType.Ternary: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + EnsureSameType(dest, src1, src2, src3); + + Debug.Assert(!dest.Type.IsInteger()); + + if (info.Inst == X86Instruction.Blendvpd && HardwareCapabilities.SupportsVexEncoding) + { + context.Assembler.WriteInstruction(X86Instruction.Vblendvpd, dest, src1, src2, src3); + } + else if (info.Inst == X86Instruction.Blendvps && HardwareCapabilities.SupportsVexEncoding) + { + context.Assembler.WriteInstruction(X86Instruction.Vblendvps, dest, src1, src2, src3); + } + else if (info.Inst == X86Instruction.Pblendvb && HardwareCapabilities.SupportsVexEncoding) + { + context.Assembler.WriteInstruction(X86Instruction.Vpblendvb, dest, src1, src2, src3); + } + else + { + EnsureSameReg(dest, src1); + + Debug.Assert(src3.GetRegister().Index == 0); + + context.Assembler.WriteInstruction(info.Inst, dest, src1, src2); + } + + break; + } + + case IntrinsicType.TernaryImm: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + EnsureSameType(dest, src1, src2); + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(!dest.Type.IsInteger() && src3.Kind == OperandKind.Constant); + + context.Assembler.WriteInstruction(info.Inst, dest, src1, src2, src3.AsByte()); + + break; + } + + case IntrinsicType.Fma: + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + Debug.Assert(HardwareCapabilities.SupportsVexEncoding); + + Debug.Assert(dest.Kind == OperandKind.Register && src1.Kind == OperandKind.Register && src2.Kind == OperandKind.Register); + Debug.Assert(src3.Kind == OperandKind.Register || src3.Kind == OperandKind.Memory); + + EnsureSameType(dest, src1, src2, src3); + Debug.Assert(dest.Type == OperandType.V128); + + Debug.Assert(dest.Value == src1.Value); + + context.Assembler.WriteInstruction(info.Inst, dest, src2, src3); + + break; + } + } + } + else + { + Action func = _instTable[(int)operation.Instruction]; + + if (func != null) + { + func(context, operation); + } + else + { + throw new ArgumentException($"Invalid instruction \"{operation.Instruction}\"."); + } + } + } + + private static void GenerateAdd(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + if (dest.Type.IsInteger()) + { + // If Destination and Source 1 Operands are the same, perform a standard add as there are no benefits to using LEA. + if (dest.Kind == src1.Kind && dest.Value == src1.Value) + { + ValidateBinOp(dest, src1, src2); + + context.Assembler.Add(dest, src2, dest.Type); + } + else + { + EnsureSameType(dest, src1, src2); + + int offset; + Operand index; + + if (src2.Kind == OperandKind.Constant) + { + offset = src2.AsInt32(); + index = default; + } + else + { + offset = 0; + index = src2; + } + + Operand memOp = MemoryOp(dest.Type, src1, index, Multiplier.x1, offset); + + context.Assembler.Lea(dest, memOp, dest.Type); + } + } + else + { + ValidateBinOp(dest, src1, src2); + + if (dest.Type == OperandType.FP32) + { + context.Assembler.Addss(dest, src1, src2); + } + else /* if (dest.Type == OperandType.FP64) */ + { + context.Assembler.Addsd(dest, src1, src2); + } + } + } + + private static void GenerateBitwiseAnd(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateBinOp(dest, src1, src2); + + Debug.Assert(dest.Type.IsInteger()); + + // Note: GenerateCompareCommon makes the assumption that BitwiseAnd will emit only a single `and` + // instruction. + context.Assembler.And(dest, src2, dest.Type); + } + + private static void GenerateBitwiseExclusiveOr(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateBinOp(dest, src1, src2); + + if (dest.Type.IsInteger()) + { + context.Assembler.Xor(dest, src2, dest.Type); + } + else + { + context.Assembler.Xorps(dest, src1, src2); + } + } + + private static void GenerateBitwiseNot(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + ValidateUnOp(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Not(dest); + } + + private static void GenerateBitwiseOr(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateBinOp(dest, src1, src2); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Or(dest, src2, dest.Type); + } + + private static void GenerateBranchIf(CodeGenContext context, Operation operation) + { + Operand comp = operation.GetSource(2); + + Debug.Assert(comp.Kind == OperandKind.Constant); + + var cond = ((Comparison)comp.AsInt32()).ToX86Condition(); + + GenerateCompareCommon(context, operation); + + context.JumpTo(cond, context.CurrBlock.GetSuccessor(1)); + } + + private static void GenerateByteSwap(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + ValidateUnOp(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Bswap(dest); + } + + private static void GenerateCall(CodeGenContext context, Operation operation) + { + context.Assembler.Call(operation.GetSource(0)); + } + + private static void GenerateClobber(CodeGenContext context, Operation operation) + { + // This is only used to indicate that a register is clobbered to the + // register allocator, we don't need to produce any code. + } + + private static void GenerateCompare(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand comp = operation.GetSource(2); + + Debug.Assert(dest.Type == OperandType.I32); + Debug.Assert(comp.Kind == OperandKind.Constant); + + var cond = ((Comparison)comp.AsInt32()).ToX86Condition(); + + GenerateCompareCommon(context, operation); + + context.Assembler.Setcc(dest, cond); + context.Assembler.Movzx8(dest, dest, OperandType.I32); + } + + private static void GenerateCompareCommon(CodeGenContext context, Operation operation) + { + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + EnsureSameType(src1, src2); + + Debug.Assert(src1.Type.IsInteger()); + + if (src2.Kind == OperandKind.Constant && src2.Value == 0) + { + if (MatchOperation(operation.ListPrevious, Instruction.BitwiseAnd, src1.Type, src1.GetRegister())) + { + // Since the `test` and `and` instruction set the status flags in the same way, we can omit the + // `test r,r` instruction when it is immediately preceded by an `and r,*` instruction. + // + // For example: + // + // and eax, 0x3 + // test eax, eax + // jz .L0 + // + // => + // + // and eax, 0x3 + // jz .L0 + } + else + { + context.Assembler.Test(src1, src1, src1.Type); + } + } + else + { + context.Assembler.Cmp(src1, src2, src1.Type); + } + } + + private static void GenerateCompareAndSwap(CodeGenContext context, Operation operation) + { + Operand src1 = operation.GetSource(0); + + if (operation.SourcesCount == 5) // CompareAndSwap128 has 5 sources, compared to CompareAndSwap64/32's 3. + { + Operand memOp = MemoryOp(OperandType.I64, src1); + + context.Assembler.Cmpxchg16b(memOp); + } + else + { + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + EnsureSameType(src2, src3); + + Operand memOp = MemoryOp(src3.Type, src1); + + context.Assembler.Cmpxchg(memOp, src3); + } + } + + private static void GenerateCompareAndSwap16(CodeGenContext context, Operation operation) + { + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + EnsureSameType(src2, src3); + + Operand memOp = MemoryOp(src3.Type, src1); + + context.Assembler.Cmpxchg16(memOp, src3); + } + + private static void GenerateCompareAndSwap8(CodeGenContext context, Operation operation) + { + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + EnsureSameType(src2, src3); + + Operand memOp = MemoryOp(src3.Type, src1); + + context.Assembler.Cmpxchg8(memOp, src3); + } + + private static void GenerateConditionalSelect(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + Operand src3 = operation.GetSource(2); + + EnsureSameReg (dest, src3); + EnsureSameType(dest, src2, src3); + + Debug.Assert(dest.Type.IsInteger()); + Debug.Assert(src1.Type == OperandType.I32); + + context.Assembler.Test (src1, src1, src1.Type); + context.Assembler.Cmovcc(dest, src2, dest.Type, X86Condition.NotEqual); + } + + private static void GenerateConvertI64ToI32(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.I32 && source.Type == OperandType.I64); + + context.Assembler.Mov(dest, source, OperandType.I32); + } + + private static void GenerateConvertToFP(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.FP32 || dest.Type == OperandType.FP64); + + if (dest.Type == OperandType.FP32) + { + Debug.Assert(source.Type.IsInteger() || source.Type == OperandType.FP64); + + if (source.Type.IsInteger()) + { + context.Assembler.Xorps (dest, dest, dest); + context.Assembler.Cvtsi2ss(dest, dest, source, source.Type); + } + else /* if (source.Type == OperandType.FP64) */ + { + context.Assembler.Cvtsd2ss(dest, dest, source); + + GenerateZeroUpper96(context, dest, dest); + } + } + else /* if (dest.Type == OperandType.FP64) */ + { + Debug.Assert(source.Type.IsInteger() || source.Type == OperandType.FP32); + + if (source.Type.IsInteger()) + { + context.Assembler.Xorps (dest, dest, dest); + context.Assembler.Cvtsi2sd(dest, dest, source, source.Type); + } + else /* if (source.Type == OperandType.FP32) */ + { + context.Assembler.Cvtss2sd(dest, dest, source); + + GenerateZeroUpper64(context, dest, dest); + } + } + } + + private static void GenerateCopy(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + EnsureSameType(dest, source); + + Debug.Assert(dest.Type.IsInteger() || source.Kind != OperandKind.Constant); + + // Moves to the same register are useless. + if (dest.Kind == source.Kind && dest.Value == source.Value) + { + return; + } + + if (dest.Kind == OperandKind.Register && + source.Kind == OperandKind.Constant && source.Value == 0) + { + // Assemble "mov reg, 0" as "xor reg, reg" as the later is more efficient. + context.Assembler.Xor(dest, dest, OperandType.I32); + } + else if (dest.Type.IsInteger()) + { + context.Assembler.Mov(dest, source, dest.Type); + } + else + { + context.Assembler.Movdqu(dest, source); + } + } + + private static void GenerateCountLeadingZeros(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + EnsureSameType(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Bsr(dest, source, dest.Type); + + int operandSize = dest.Type == OperandType.I32 ? 32 : 64; + int operandMask = operandSize - 1; + + // When the input operand is 0, the result is undefined, however the + // ZF flag is set. We are supposed to return the operand size on that + // case. So, add an additional jump to handle that case, by moving the + // operand size constant to the destination register. + Operand neLabel = Label(); + + context.Assembler.Jcc(X86Condition.NotEqual, neLabel); + + context.Assembler.Mov(dest, Const(operandSize | operandMask), OperandType.I32); + + context.Assembler.MarkLabel(neLabel); + + // BSR returns the zero based index of the last bit set on the operand, + // starting from the least significant bit. However we are supposed to + // return the number of 0 bits on the high end. So, we invert the result + // of the BSR using XOR to get the correct value. + context.Assembler.Xor(dest, Const(operandMask), OperandType.I32); + } + + private static void GenerateDivide(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand dividend = operation.GetSource(0); + Operand divisor = operation.GetSource(1); + + if (!dest.Type.IsInteger()) + { + ValidateBinOp(dest, dividend, divisor); + } + + if (dest.Type.IsInteger()) + { + divisor = operation.GetSource(2); + + EnsureSameType(dest, divisor); + + if (divisor.Type == OperandType.I32) + { + context.Assembler.Cdq(); + } + else + { + context.Assembler.Cqo(); + } + + context.Assembler.Idiv(divisor); + } + else if (dest.Type == OperandType.FP32) + { + context.Assembler.Divss(dest, dividend, divisor); + } + else /* if (dest.Type == OperandType.FP64) */ + { + context.Assembler.Divsd(dest, dividend, divisor); + } + } + + private static void GenerateDivideUI(CodeGenContext context, Operation operation) + { + Operand divisor = operation.GetSource(2); + + Operand rdx = Register(X86Register.Rdx); + + Debug.Assert(divisor.Type.IsInteger()); + + context.Assembler.Xor(rdx, rdx, OperandType.I32); + context.Assembler.Div(divisor); + } + + private static void GenerateFill(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand offset = operation.GetSource(0); + + Debug.Assert(offset.Kind == OperandKind.Constant); + + int offs = offset.AsInt32() + context.CallArgsRegionSize; + + Operand rsp = Register(X86Register.Rsp); + + Operand memOp = MemoryOp(dest.Type, rsp, default, Multiplier.x1, offs); + + GenerateLoad(context, memOp, dest); + } + + private static void GenerateLoad(CodeGenContext context, Operation operation) + { + Operand value = operation.Destination; + Operand address = Memory(operation.GetSource(0), value.Type); + + GenerateLoad(context, address, value); + } + + private static void GenerateLoad16(CodeGenContext context, Operation operation) + { + Operand value = operation.Destination; + Operand address = Memory(operation.GetSource(0), value.Type); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.Movzx16(value, address, value.Type); + } + + private static void GenerateLoad8(CodeGenContext context, Operation operation) + { + Operand value = operation.Destination; + Operand address = Memory(operation.GetSource(0), value.Type); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.Movzx8(value, address, value.Type); + } + + private static void GenerateMemoryBarrier(CodeGenContext context, Operation operation) + { + context.Assembler.LockOr(MemoryOp(OperandType.I64, Register(X86Register.Rsp)), Const(0), OperandType.I32); + } + + private static void GenerateMultiply(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + if (src2.Kind != OperandKind.Constant) + { + EnsureSameReg(dest, src1); + } + + EnsureSameType(dest, src1, src2); + + if (dest.Type.IsInteger()) + { + if (src2.Kind == OperandKind.Constant) + { + context.Assembler.Imul(dest, src1, src2, dest.Type); + } + else + { + context.Assembler.Imul(dest, src2, dest.Type); + } + } + else if (dest.Type == OperandType.FP32) + { + context.Assembler.Mulss(dest, src1, src2); + } + else /* if (dest.Type == OperandType.FP64) */ + { + context.Assembler.Mulsd(dest, src1, src2); + } + } + + private static void GenerateMultiply64HighSI(CodeGenContext context, Operation operation) + { + Operand source = operation.GetSource(1); + + Debug.Assert(source.Type == OperandType.I64); + + context.Assembler.Imul(source); + } + + private static void GenerateMultiply64HighUI(CodeGenContext context, Operation operation) + { + Operand source = operation.GetSource(1); + + Debug.Assert(source.Type == OperandType.I64); + + context.Assembler.Mul(source); + } + + private static void GenerateNegate(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + ValidateUnOp(dest, source); + + Debug.Assert(dest.Type.IsInteger()); + + context.Assembler.Neg(dest); + } + + private static void GenerateReturn(CodeGenContext context, Operation operation) + { + WriteEpilogue(context); + + context.Assembler.Return(); + } + + private static void GenerateRotateRight(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Ror(dest, src2, dest.Type); + } + + private static void GenerateShiftLeft(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Shl(dest, src2, dest.Type); + } + + private static void GenerateShiftRightSI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Sar(dest, src2, dest.Type); + } + + private static void GenerateShiftRightUI(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateShift(dest, src1, src2); + + context.Assembler.Shr(dest, src2, dest.Type); + } + + private static void GenerateSignExtend16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Movsx16(dest, source, dest.Type); + } + + private static void GenerateSignExtend32(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Movsx32(dest, source, dest.Type); + } + + private static void GenerateSignExtend8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Movsx8(dest, source, dest.Type); + } + + private static void GenerateSpill(CodeGenContext context, Operation operation) + { + GenerateSpill(context, operation, context.CallArgsRegionSize); + } + + private static void GenerateSpillArg(CodeGenContext context, Operation operation) + { + GenerateSpill(context, operation, 0); + } + + private static void GenerateSpill(CodeGenContext context, Operation operation, int baseOffset) + { + Operand offset = operation.GetSource(0); + Operand source = operation.GetSource(1); + + Debug.Assert(offset.Kind == OperandKind.Constant); + + int offs = offset.AsInt32() + baseOffset; + + Operand rsp = Register(X86Register.Rsp); + + Operand memOp = MemoryOp(source.Type, rsp, default, Multiplier.x1, offs); + + GenerateStore(context, memOp, source); + } + + private static void GenerateStackAlloc(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand offset = operation.GetSource(0); + + Debug.Assert(offset.Kind == OperandKind.Constant); + + int offs = offset.AsInt32() + context.CallArgsRegionSize; + + Operand rsp = Register(X86Register.Rsp); + + Operand memOp = MemoryOp(OperandType.I64, rsp, default, Multiplier.x1, offs); + + context.Assembler.Lea(dest, memOp, OperandType.I64); + } + + private static void GenerateStore(CodeGenContext context, Operation operation) + { + Operand value = operation.GetSource(1); + Operand address = Memory(operation.GetSource(0), value.Type); + + GenerateStore(context, address, value); + } + + private static void GenerateStore16(CodeGenContext context, Operation operation) + { + Operand value = operation.GetSource(1); + Operand address = Memory(operation.GetSource(0), value.Type); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.Mov16(address, value); + } + + private static void GenerateStore8(CodeGenContext context, Operation operation) + { + Operand value = operation.GetSource(1); + Operand address = Memory(operation.GetSource(0), value.Type); + + Debug.Assert(value.Type.IsInteger()); + + context.Assembler.Mov8(address, value); + } + + private static void GenerateSubtract(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + ValidateBinOp(dest, src1, src2); + + if (dest.Type.IsInteger()) + { + context.Assembler.Sub(dest, src2, dest.Type); + } + else if (dest.Type == OperandType.FP32) + { + context.Assembler.Subss(dest, src1, src2); + } + else /* if (dest.Type == OperandType.FP64) */ + { + context.Assembler.Subsd(dest, src1, src2); + } + } + + private static void GenerateTailcall(CodeGenContext context, Operation operation) + { + WriteEpilogue(context); + + context.Assembler.Jmp(operation.GetSource(0)); + } + + private static void GenerateVectorCreateScalar(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(!dest.Type.IsInteger() && source.Type.IsInteger()); + + if (source.Type == OperandType.I32) + { + context.Assembler.Movd(dest, source); // (__m128i _mm_cvtsi32_si128(int a)) + } + else /* if (source.Type == OperandType.I64) */ + { + context.Assembler.Movq(dest, source); // (__m128i _mm_cvtsi64_si128(__int64 a)) + } + } + + private static void GenerateVectorExtract(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; //Value + Operand src1 = operation.GetSource(0); //Vector + Operand src2 = operation.GetSource(1); //Index + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src2.Kind == OperandKind.Constant); + + byte index = src2.AsByte(); + + Debug.Assert(index < OperandType.V128.GetSizeInBytes() / dest.Type.GetSizeInBytes()); + + if (dest.Type == OperandType.I32) + { + if (index == 0) + { + context.Assembler.Movd(dest, src1); + } + else if (HardwareCapabilities.SupportsSse41) + { + context.Assembler.Pextrd(dest, src1, index); + } + else + { + int mask0 = 0b11_10_01_00; + int mask1 = 0b11_10_01_00; + + mask0 = BitUtils.RotateRight(mask0, index * 2, 8); + mask1 = BitUtils.RotateRight(mask1, 8 - index * 2, 8); + + context.Assembler.Pshufd(src1, src1, (byte)mask0); + context.Assembler.Movd (dest, src1); + context.Assembler.Pshufd(src1, src1, (byte)mask1); + } + } + else if (dest.Type == OperandType.I64) + { + if (index == 0) + { + context.Assembler.Movq(dest, src1); + } + else if (HardwareCapabilities.SupportsSse41) + { + context.Assembler.Pextrq(dest, src1, index); + } + else + { + const byte mask = 0b01_00_11_10; + + context.Assembler.Pshufd(src1, src1, mask); + context.Assembler.Movq (dest, src1); + context.Assembler.Pshufd(src1, src1, mask); + } + } + else + { + // Floating-point types. + if ((index >= 2 && dest.Type == OperandType.FP32) || + (index == 1 && dest.Type == OperandType.FP64)) + { + context.Assembler.Movhlps(dest, dest, src1); + context.Assembler.Movq (dest, dest); + } + else + { + context.Assembler.Movq(dest, src1); + } + + if (dest.Type == OperandType.FP32) + { + context.Assembler.Pshufd(dest, dest, (byte)(0xfc | (index & 1))); + } + } + } + + private static void GenerateVectorExtract16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; //Value + Operand src1 = operation.GetSource(0); //Vector + Operand src2 = operation.GetSource(1); //Index + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src2.Kind == OperandKind.Constant); + + byte index = src2.AsByte(); + + Debug.Assert(index < 8); + + context.Assembler.Pextrw(dest, src1, index); + } + + private static void GenerateVectorExtract8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; //Value + Operand src1 = operation.GetSource(0); //Vector + Operand src2 = operation.GetSource(1); //Index + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src2.Kind == OperandKind.Constant); + + byte index = src2.AsByte(); + + Debug.Assert(index < 16); + + if (HardwareCapabilities.SupportsSse41) + { + context.Assembler.Pextrb(dest, src1, index); + } + else + { + context.Assembler.Pextrw(dest, src1, (byte)(index >> 1)); + + if ((index & 1) != 0) + { + context.Assembler.Shr(dest, Const(8), OperandType.I32); + } + else + { + context.Assembler.Movzx8(dest, dest, OperandType.I32); + } + } + } + + private static void GenerateVectorInsert(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); //Vector + Operand src2 = operation.GetSource(1); //Value + Operand src3 = operation.GetSource(2); //Index + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src3.Kind == OperandKind.Constant); + + byte index = src3.AsByte(); + + void InsertIntSse2(int words) + { + if (dest.GetRegister() != src1.GetRegister()) + { + context.Assembler.Movdqu(dest, src1); + } + + for (int word = 0; word < words; word++) + { + // Insert lower 16-bits. + context.Assembler.Pinsrw(dest, dest, src2, (byte)(index * words + word)); + + // Move next word down. + context.Assembler.Ror(src2, Const(16), src2.Type); + } + } + + if (src2.Type == OperandType.I32) + { + Debug.Assert(index < 4); + + if (HardwareCapabilities.SupportsSse41) + { + context.Assembler.Pinsrd(dest, src1, src2, index); + } + else + { + InsertIntSse2(2); + } + } + else if (src2.Type == OperandType.I64) + { + Debug.Assert(index < 2); + + if (HardwareCapabilities.SupportsSse41) + { + context.Assembler.Pinsrq(dest, src1, src2, index); + } + else + { + InsertIntSse2(4); + } + } + else if (src2.Type == OperandType.FP32) + { + Debug.Assert(index < 4); + + if (index != 0) + { + if (HardwareCapabilities.SupportsSse41) + { + context.Assembler.Insertps(dest, src1, src2, (byte)(index << 4)); + } + else + { + if (src1.GetRegister() == src2.GetRegister()) + { + int mask = 0b11_10_01_00; + + mask &= ~(0b11 << index * 2); + + context.Assembler.Pshufd(dest, src1, (byte)mask); + } + else + { + int mask0 = 0b11_10_01_00; + int mask1 = 0b11_10_01_00; + + mask0 = BitUtils.RotateRight(mask0, index * 2, 8); + mask1 = BitUtils.RotateRight(mask1, 8 - index * 2, 8); + + context.Assembler.Pshufd(src1, src1, (byte)mask0); // Lane to be inserted in position 0. + context.Assembler.Movss (dest, src1, src2); // dest[127:0] = src1[127:32] | src2[31:0] + context.Assembler.Pshufd(dest, dest, (byte)mask1); // Inserted lane in original position. + + if (dest.GetRegister() != src1.GetRegister()) + { + context.Assembler.Pshufd(src1, src1, (byte)mask1); // Restore src1. + } + } + } + } + else + { + context.Assembler.Movss(dest, src1, src2); + } + } + else /* if (src2.Type == OperandType.FP64) */ + { + Debug.Assert(index < 2); + + if (index != 0) + { + context.Assembler.Movlhps(dest, src1, src2); + } + else + { + context.Assembler.Movsd(dest, src1, src2); + } + } + } + + private static void GenerateVectorInsert16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); //Vector + Operand src2 = operation.GetSource(1); //Value + Operand src3 = operation.GetSource(2); //Index + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src3.Kind == OperandKind.Constant); + + byte index = src3.AsByte(); + + context.Assembler.Pinsrw(dest, src1, src2, index); + } + + private static void GenerateVectorInsert8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand src1 = operation.GetSource(0); //Vector + Operand src2 = operation.GetSource(1); //Value + Operand src3 = operation.GetSource(2); //Index + + // It's not possible to emulate this instruction without + // SSE 4.1 support without the use of a temporary register, + // so we instead handle that case on the pre-allocator when + // SSE 4.1 is not supported on the CPU. + Debug.Assert(HardwareCapabilities.SupportsSse41); + + if (!HardwareCapabilities.SupportsVexEncoding) + { + EnsureSameReg(dest, src1); + } + + Debug.Assert(src1.Type == OperandType.V128); + Debug.Assert(src3.Kind == OperandKind.Constant); + + byte index = src3.AsByte(); + + context.Assembler.Pinsrb(dest, src1, src2, index); + } + + private static void GenerateVectorOne(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + + Debug.Assert(!dest.Type.IsInteger()); + + context.Assembler.Pcmpeqw(dest, dest, dest); + } + + private static void GenerateVectorZero(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + + Debug.Assert(!dest.Type.IsInteger()); + + context.Assembler.Xorps(dest, dest, dest); + } + + private static void GenerateVectorZeroUpper64(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.V128 && source.Type == OperandType.V128); + + GenerateZeroUpper64(context, dest, source); + } + + private static void GenerateVectorZeroUpper96(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type == OperandType.V128 && source.Type == OperandType.V128); + + GenerateZeroUpper96(context, dest, source); + } + + private static void GenerateZeroExtend16(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Movzx16(dest, source, OperandType.I32); + } + + private static void GenerateZeroExtend32(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + // We can eliminate the move if source is already 32-bit and the registers are the same. + if (dest.Value == source.Value && source.Type == OperandType.I32) + { + return; + } + + context.Assembler.Mov(dest, source, OperandType.I32); + } + + private static void GenerateZeroExtend8(CodeGenContext context, Operation operation) + { + Operand dest = operation.Destination; + Operand source = operation.GetSource(0); + + Debug.Assert(dest.Type.IsInteger() && source.Type.IsInteger()); + + context.Assembler.Movzx8(dest, source, OperandType.I32); + } + + private static void GenerateLoad(CodeGenContext context, Operand address, Operand value) + { + switch (value.Type) + { + case OperandType.I32: context.Assembler.Mov (value, address, OperandType.I32); break; + case OperandType.I64: context.Assembler.Mov (value, address, OperandType.I64); break; + case OperandType.FP32: context.Assembler.Movd (value, address); break; + case OperandType.FP64: context.Assembler.Movq (value, address); break; + case OperandType.V128: context.Assembler.Movdqu(value, address); break; + + default: Debug.Assert(false); break; + } + } + + private static void GenerateStore(CodeGenContext context, Operand address, Operand value) + { + switch (value.Type) + { + case OperandType.I32: context.Assembler.Mov (address, value, OperandType.I32); break; + case OperandType.I64: context.Assembler.Mov (address, value, OperandType.I64); break; + case OperandType.FP32: context.Assembler.Movd (address, value); break; + case OperandType.FP64: context.Assembler.Movq (address, value); break; + case OperandType.V128: context.Assembler.Movdqu(address, value); break; + + default: Debug.Assert(false); break; + } + } + + private static void GenerateZeroUpper64(CodeGenContext context, Operand dest, Operand source) + { + context.Assembler.Movq(dest, source); + } + + private static void GenerateZeroUpper96(CodeGenContext context, Operand dest, Operand source) + { + context.Assembler.Movq(dest, source); + context.Assembler.Pshufd(dest, dest, 0xfc); + } + + private static bool MatchOperation(Operation node, Instruction inst, OperandType destType, Register destReg) + { + if (node == default || node.DestinationsCount == 0) + { + return false; + } + + if (node.Instruction != inst) + { + return false; + } + + Operand dest = node.Destination; + + return dest.Kind == OperandKind.Register && + dest.Type == destType && + dest.GetRegister() == destReg; + } + + [Conditional("DEBUG")] + private static void ValidateUnOp(Operand dest, Operand source) + { + EnsureSameReg (dest, source); + EnsureSameType(dest, source); + } + + [Conditional("DEBUG")] + private static void ValidateBinOp(Operand dest, Operand src1, Operand src2) + { + EnsureSameReg (dest, src1); + EnsureSameType(dest, src1, src2); + } + + [Conditional("DEBUG")] + private static void ValidateShift(Operand dest, Operand src1, Operand src2) + { + EnsureSameReg (dest, src1); + EnsureSameType(dest, src1); + + Debug.Assert(dest.Type.IsInteger() && src2.Type == OperandType.I32); + } + + private static void EnsureSameReg(Operand op1, Operand op2) + { + if (!op1.Type.IsInteger() && HardwareCapabilities.SupportsVexEncoding) + { + return; + } + + Debug.Assert(op1.Kind == OperandKind.Register || op1.Kind == OperandKind.Memory); + Debug.Assert(op1.Kind == op2.Kind); + Debug.Assert(op1.Value == op2.Value); + } + + private static void EnsureSameType(Operand op1, Operand op2) + { + Debug.Assert(op1.Type == op2.Type); + } + + private static void EnsureSameType(Operand op1, Operand op2, Operand op3) + { + Debug.Assert(op1.Type == op2.Type); + Debug.Assert(op1.Type == op3.Type); + } + + private static void EnsureSameType(Operand op1, Operand op2, Operand op3, Operand op4) + { + Debug.Assert(op1.Type == op2.Type); + Debug.Assert(op1.Type == op3.Type); + Debug.Assert(op1.Type == op4.Type); + } + + private static UnwindInfo WritePrologue(CodeGenContext context) + { + List pushEntries = new List(); + + Operand rsp = Register(X86Register.Rsp); + + int mask = CallingConvention.GetIntCalleeSavedRegisters() & context.AllocResult.IntUsedRegisters; + + while (mask != 0) + { + int bit = BitOperations.TrailingZeroCount(mask); + + context.Assembler.Push(Register((X86Register)bit)); + + pushEntries.Add(new UnwindPushEntry(UnwindPseudoOp.PushReg, context.StreamOffset, regIndex: bit)); + + mask &= ~(1 << bit); + } + + int reservedStackSize = context.CallArgsRegionSize + context.AllocResult.SpillRegionSize; + + reservedStackSize += context.XmmSaveRegionSize; + + if (reservedStackSize >= StackGuardSize) + { + GenerateInlineStackProbe(context, reservedStackSize); + } + + if (reservedStackSize != 0) + { + context.Assembler.Sub(rsp, Const(reservedStackSize), OperandType.I64); + + pushEntries.Add(new UnwindPushEntry(UnwindPseudoOp.AllocStack, context.StreamOffset, stackOffsetOrAllocSize: reservedStackSize)); + } + + int offset = reservedStackSize; + + mask = CallingConvention.GetVecCalleeSavedRegisters() & context.AllocResult.VecUsedRegisters; + + while (mask != 0) + { + int bit = BitOperations.TrailingZeroCount(mask); + + offset -= 16; + + Operand memOp = MemoryOp(OperandType.V128, rsp, default, Multiplier.x1, offset); + + context.Assembler.Movdqu(memOp, Xmm((X86Register)bit)); + + pushEntries.Add(new UnwindPushEntry(UnwindPseudoOp.SaveXmm128, context.StreamOffset, bit, offset)); + + mask &= ~(1 << bit); + } + + return new UnwindInfo(pushEntries.ToArray(), context.StreamOffset); + } + + private static void WriteEpilogue(CodeGenContext context) + { + Operand rsp = Register(X86Register.Rsp); + + int reservedStackSize = context.CallArgsRegionSize + context.AllocResult.SpillRegionSize; + + reservedStackSize += context.XmmSaveRegionSize; + + int offset = reservedStackSize; + + int mask = CallingConvention.GetVecCalleeSavedRegisters() & context.AllocResult.VecUsedRegisters; + + while (mask != 0) + { + int bit = BitOperations.TrailingZeroCount(mask); + + offset -= 16; + + Operand memOp = MemoryOp(OperandType.V128, rsp, default, Multiplier.x1, offset); + + context.Assembler.Movdqu(Xmm((X86Register)bit), memOp); + + mask &= ~(1 << bit); + } + + if (reservedStackSize != 0) + { + context.Assembler.Add(rsp, Const(reservedStackSize), OperandType.I64); + } + + mask = CallingConvention.GetIntCalleeSavedRegisters() & context.AllocResult.IntUsedRegisters; + + while (mask != 0) + { + int bit = BitUtils.HighestBitSet(mask); + + context.Assembler.Pop(Register((X86Register)bit)); + + mask &= ~(1 << bit); + } + } + + private static void GenerateInlineStackProbe(CodeGenContext context, int size) + { + // Windows does lazy stack allocation, and there are just 2 + // guard pages on the end of the stack. So, if the allocation + // size we make is greater than this guard size, we must ensure + // that the OS will map all pages that we'll use. We do that by + // doing a dummy read on those pages, forcing a page fault and + // the OS to map them. If they are already mapped, nothing happens. + const int pageMask = PageSize - 1; + + size = (size + pageMask) & ~pageMask; + + Operand rsp = Register(X86Register.Rsp); + Operand temp = Register(CallingConvention.GetIntReturnRegister()); + + for (int offset = PageSize; offset < size; offset += PageSize) + { + Operand memOp = MemoryOp(OperandType.I32, rsp, default, Multiplier.x1, -offset); + + context.Assembler.Mov(temp, memOp, OperandType.I32); + } + } + + private static Operand Memory(Operand operand, OperandType type) + { + if (operand.Kind == OperandKind.Memory) + { + return operand; + } + + return MemoryOp(type, operand); + } + + private static Operand Register(X86Register register, OperandType type = OperandType.I64) + { + return Operand.Factory.Register((int)register, RegisterType.Integer, type); + } + + private static Operand Xmm(X86Register register) + { + return Operand.Factory.Register((int)register, RegisterType.Vector, OperandType.V128); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/X86/HardwareCapabilities.cs b/src/ARMeilleure/CodeGen/X86/HardwareCapabilities.cs new file mode 100644 index 00000000..07cdcd09 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/HardwareCapabilities.cs @@ -0,0 +1,144 @@ +using Ryujinx.Memory; +using System; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics.X86; + +namespace ARMeilleure.CodeGen.X86 +{ + static class HardwareCapabilities + { + private delegate uint GetXcr0(); + + static HardwareCapabilities() + { + if (!X86Base.IsSupported) + { + return; + } + + (int maxNum, _, _, _) = X86Base.CpuId(0x00000000, 0x00000000); + + (_, _, int ecx1, int edx1) = X86Base.CpuId(0x00000001, 0x00000000); + FeatureInfo1Edx = (FeatureFlags1Edx)edx1; + FeatureInfo1Ecx = (FeatureFlags1Ecx)ecx1; + + if (maxNum >= 7) + { + (_, int ebx7, int ecx7, _) = X86Base.CpuId(0x00000007, 0x00000000); + FeatureInfo7Ebx = (FeatureFlags7Ebx)ebx7; + FeatureInfo7Ecx = (FeatureFlags7Ecx)ecx7; + } + + Xcr0InfoEax = (Xcr0FlagsEax)GetXcr0Eax(); + } + + private static uint GetXcr0Eax() + { + if (!FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Xsave)) + { + // XSAVE feature required for xgetbv + return 0; + } + + ReadOnlySpan asmGetXcr0 = new byte[] + { + 0x31, 0xc9, // xor ecx, ecx + 0xf, 0x01, 0xd0, // xgetbv + 0xc3, // ret + }; + + using MemoryBlock memGetXcr0 = new MemoryBlock((ulong)asmGetXcr0.Length); + + memGetXcr0.Write(0, asmGetXcr0); + + memGetXcr0.Reprotect(0, (ulong)asmGetXcr0.Length, MemoryPermission.ReadAndExecute); + + var fGetXcr0 = Marshal.GetDelegateForFunctionPointer(memGetXcr0.Pointer); + + return fGetXcr0(); + } + + [Flags] + public enum FeatureFlags1Edx + { + Sse = 1 << 25, + Sse2 = 1 << 26 + } + + [Flags] + public enum FeatureFlags1Ecx + { + Sse3 = 1 << 0, + Pclmulqdq = 1 << 1, + Ssse3 = 1 << 9, + Fma = 1 << 12, + Sse41 = 1 << 19, + Sse42 = 1 << 20, + Popcnt = 1 << 23, + Aes = 1 << 25, + Xsave = 1 << 26, + Osxsave = 1 << 27, + Avx = 1 << 28, + F16c = 1 << 29 + } + + [Flags] + public enum FeatureFlags7Ebx + { + Avx2 = 1 << 5, + Avx512f = 1 << 16, + Avx512dq = 1 << 17, + Sha = 1 << 29, + Avx512bw = 1 << 30, + Avx512vl = 1 << 31 + } + + [Flags] + public enum FeatureFlags7Ecx + { + Gfni = 1 << 8, + } + + [Flags] + public enum Xcr0FlagsEax + { + Sse = 1 << 1, + YmmHi128 = 1 << 2, + Opmask = 1 << 5, + ZmmHi256 = 1 << 6, + Hi16Zmm = 1 << 7 + } + + public static FeatureFlags1Edx FeatureInfo1Edx { get; } + public static FeatureFlags1Ecx FeatureInfo1Ecx { get; } + public static FeatureFlags7Ebx FeatureInfo7Ebx { get; } = 0; + public static FeatureFlags7Ecx FeatureInfo7Ecx { get; } = 0; + public static Xcr0FlagsEax Xcr0InfoEax { get; } = 0; + + public static bool SupportsSse => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse); + public static bool SupportsSse2 => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse2); + public static bool SupportsSse3 => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Sse3); + public static bool SupportsPclmulqdq => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Pclmulqdq); + public static bool SupportsSsse3 => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Ssse3); + public static bool SupportsFma => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Fma); + public static bool SupportsSse41 => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Sse41); + public static bool SupportsSse42 => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Sse42); + public static bool SupportsPopcnt => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Popcnt); + public static bool SupportsAesni => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Aes); + public static bool SupportsAvx => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Avx | FeatureFlags1Ecx.Xsave | FeatureFlags1Ecx.Osxsave) && Xcr0InfoEax.HasFlag(Xcr0FlagsEax.Sse | Xcr0FlagsEax.YmmHi128); + public static bool SupportsAvx2 => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx2) && SupportsAvx; + public static bool SupportsAvx512F => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512f) && FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Xsave | FeatureFlags1Ecx.Osxsave) + && Xcr0InfoEax.HasFlag(Xcr0FlagsEax.Sse | Xcr0FlagsEax.YmmHi128 | Xcr0FlagsEax.Opmask | Xcr0FlagsEax.ZmmHi256 | Xcr0FlagsEax.Hi16Zmm); + public static bool SupportsAvx512Vl => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512vl) && SupportsAvx512F; + public static bool SupportsAvx512Bw => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512bw) && SupportsAvx512F; + public static bool SupportsAvx512Dq => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512dq) && SupportsAvx512F; + public static bool SupportsF16c => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.F16c); + public static bool SupportsSha => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Sha); + public static bool SupportsGfni => FeatureInfo7Ecx.HasFlag(FeatureFlags7Ecx.Gfni); + + public static bool ForceLegacySse { get; set; } + + public static bool SupportsVexEncoding => SupportsAvx && !ForceLegacySse; + public static bool SupportsEvexEncoding => SupportsAvx512F && !ForceLegacySse; + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/X86/IntrinsicInfo.cs b/src/ARMeilleure/CodeGen/X86/IntrinsicInfo.cs new file mode 100644 index 00000000..302bf4d3 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/IntrinsicInfo.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.CodeGen.X86 +{ + readonly struct IntrinsicInfo + { + public X86Instruction Inst { get; } + public IntrinsicType Type { get; } + + public IntrinsicInfo(X86Instruction inst, IntrinsicType type) + { + Inst = inst; + Type = type; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/X86/IntrinsicTable.cs b/src/ARMeilleure/CodeGen/X86/IntrinsicTable.cs new file mode 100644 index 00000000..e3d94b7a --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/IntrinsicTable.cs @@ -0,0 +1,200 @@ +using ARMeilleure.Common; +using ARMeilleure.IntermediateRepresentation; + +namespace ARMeilleure.CodeGen.X86 +{ + static class IntrinsicTable + { + private static IntrinsicInfo[] _intrinTable; + + static IntrinsicTable() + { + _intrinTable = new IntrinsicInfo[EnumUtils.GetCount(typeof(Intrinsic))]; + + Add(Intrinsic.X86Addpd, new IntrinsicInfo(X86Instruction.Addpd, IntrinsicType.Binary)); + Add(Intrinsic.X86Addps, new IntrinsicInfo(X86Instruction.Addps, IntrinsicType.Binary)); + Add(Intrinsic.X86Addsd, new IntrinsicInfo(X86Instruction.Addsd, IntrinsicType.Binary)); + Add(Intrinsic.X86Addss, new IntrinsicInfo(X86Instruction.Addss, IntrinsicType.Binary)); + Add(Intrinsic.X86Aesdec, new IntrinsicInfo(X86Instruction.Aesdec, IntrinsicType.Binary)); + Add(Intrinsic.X86Aesdeclast, new IntrinsicInfo(X86Instruction.Aesdeclast, IntrinsicType.Binary)); + Add(Intrinsic.X86Aesenc, new IntrinsicInfo(X86Instruction.Aesenc, IntrinsicType.Binary)); + Add(Intrinsic.X86Aesenclast, new IntrinsicInfo(X86Instruction.Aesenclast, IntrinsicType.Binary)); + Add(Intrinsic.X86Aesimc, new IntrinsicInfo(X86Instruction.Aesimc, IntrinsicType.Unary)); + Add(Intrinsic.X86Andnpd, new IntrinsicInfo(X86Instruction.Andnpd, IntrinsicType.Binary)); + Add(Intrinsic.X86Andnps, new IntrinsicInfo(X86Instruction.Andnps, IntrinsicType.Binary)); + Add(Intrinsic.X86Andpd, new IntrinsicInfo(X86Instruction.Andpd, IntrinsicType.Binary)); + Add(Intrinsic.X86Andps, new IntrinsicInfo(X86Instruction.Andps, IntrinsicType.Binary)); + Add(Intrinsic.X86Blendvpd, new IntrinsicInfo(X86Instruction.Blendvpd, IntrinsicType.Ternary)); + Add(Intrinsic.X86Blendvps, new IntrinsicInfo(X86Instruction.Blendvps, IntrinsicType.Ternary)); + Add(Intrinsic.X86Cmppd, new IntrinsicInfo(X86Instruction.Cmppd, IntrinsicType.TernaryImm)); + Add(Intrinsic.X86Cmpps, new IntrinsicInfo(X86Instruction.Cmpps, IntrinsicType.TernaryImm)); + Add(Intrinsic.X86Cmpsd, new IntrinsicInfo(X86Instruction.Cmpsd, IntrinsicType.TernaryImm)); + Add(Intrinsic.X86Cmpss, new IntrinsicInfo(X86Instruction.Cmpss, IntrinsicType.TernaryImm)); + Add(Intrinsic.X86Comisdeq, new IntrinsicInfo(X86Instruction.Comisd, IntrinsicType.Comis_)); + Add(Intrinsic.X86Comisdge, new IntrinsicInfo(X86Instruction.Comisd, IntrinsicType.Comis_)); + Add(Intrinsic.X86Comisdlt, new IntrinsicInfo(X86Instruction.Comisd, IntrinsicType.Comis_)); + Add(Intrinsic.X86Comisseq, new IntrinsicInfo(X86Instruction.Comiss, IntrinsicType.Comis_)); + Add(Intrinsic.X86Comissge, new IntrinsicInfo(X86Instruction.Comiss, IntrinsicType.Comis_)); + Add(Intrinsic.X86Comisslt, new IntrinsicInfo(X86Instruction.Comiss, IntrinsicType.Comis_)); + Add(Intrinsic.X86Crc32, new IntrinsicInfo(X86Instruction.Crc32, IntrinsicType.Crc32)); + Add(Intrinsic.X86Crc32_16, new IntrinsicInfo(X86Instruction.Crc32_16, IntrinsicType.Crc32)); + Add(Intrinsic.X86Crc32_8, new IntrinsicInfo(X86Instruction.Crc32_8, IntrinsicType.Crc32)); + Add(Intrinsic.X86Cvtdq2pd, new IntrinsicInfo(X86Instruction.Cvtdq2pd, IntrinsicType.Unary)); + Add(Intrinsic.X86Cvtdq2ps, new IntrinsicInfo(X86Instruction.Cvtdq2ps, IntrinsicType.Unary)); + Add(Intrinsic.X86Cvtpd2dq, new IntrinsicInfo(X86Instruction.Cvtpd2dq, IntrinsicType.Unary)); + Add(Intrinsic.X86Cvtpd2ps, new IntrinsicInfo(X86Instruction.Cvtpd2ps, IntrinsicType.Unary)); + Add(Intrinsic.X86Cvtps2dq, new IntrinsicInfo(X86Instruction.Cvtps2dq, IntrinsicType.Unary)); + Add(Intrinsic.X86Cvtps2pd, new IntrinsicInfo(X86Instruction.Cvtps2pd, IntrinsicType.Unary)); + Add(Intrinsic.X86Cvtsd2si, new IntrinsicInfo(X86Instruction.Cvtsd2si, IntrinsicType.UnaryToGpr)); + Add(Intrinsic.X86Cvtsd2ss, new IntrinsicInfo(X86Instruction.Cvtsd2ss, IntrinsicType.Binary)); + Add(Intrinsic.X86Cvtsi2sd, new IntrinsicInfo(X86Instruction.Cvtsi2sd, IntrinsicType.BinaryGpr)); + Add(Intrinsic.X86Cvtsi2si, new IntrinsicInfo(X86Instruction.Movd, IntrinsicType.UnaryToGpr)); + Add(Intrinsic.X86Cvtsi2ss, new IntrinsicInfo(X86Instruction.Cvtsi2ss, IntrinsicType.BinaryGpr)); + Add(Intrinsic.X86Cvtss2sd, new IntrinsicInfo(X86Instruction.Cvtss2sd, IntrinsicType.Binary)); + Add(Intrinsic.X86Cvtss2si, new IntrinsicInfo(X86Instruction.Cvtss2si, IntrinsicType.UnaryToGpr)); + Add(Intrinsic.X86Divpd, new IntrinsicInfo(X86Instruction.Divpd, IntrinsicType.Binary)); + Add(Intrinsic.X86Divps, new IntrinsicInfo(X86Instruction.Divps, IntrinsicType.Binary)); + Add(Intrinsic.X86Divsd, new IntrinsicInfo(X86Instruction.Divsd, IntrinsicType.Binary)); + Add(Intrinsic.X86Divss, new IntrinsicInfo(X86Instruction.Divss, IntrinsicType.Binary)); + Add(Intrinsic.X86Gf2p8affineqb, new IntrinsicInfo(X86Instruction.Gf2p8affineqb, IntrinsicType.TernaryImm)); + Add(Intrinsic.X86Haddpd, new IntrinsicInfo(X86Instruction.Haddpd, IntrinsicType.Binary)); + Add(Intrinsic.X86Haddps, new IntrinsicInfo(X86Instruction.Haddps, IntrinsicType.Binary)); + Add(Intrinsic.X86Insertps, new IntrinsicInfo(X86Instruction.Insertps, IntrinsicType.TernaryImm)); + Add(Intrinsic.X86Ldmxcsr, new IntrinsicInfo(X86Instruction.None, IntrinsicType.Mxcsr)); + Add(Intrinsic.X86Maxpd, new IntrinsicInfo(X86Instruction.Maxpd, IntrinsicType.Binary)); + Add(Intrinsic.X86Maxps, new IntrinsicInfo(X86Instruction.Maxps, IntrinsicType.Binary)); + Add(Intrinsic.X86Maxsd, new IntrinsicInfo(X86Instruction.Maxsd, IntrinsicType.Binary)); + Add(Intrinsic.X86Maxss, new IntrinsicInfo(X86Instruction.Maxss, IntrinsicType.Binary)); + Add(Intrinsic.X86Minpd, new IntrinsicInfo(X86Instruction.Minpd, IntrinsicType.Binary)); + Add(Intrinsic.X86Minps, new IntrinsicInfo(X86Instruction.Minps, IntrinsicType.Binary)); + Add(Intrinsic.X86Minsd, new IntrinsicInfo(X86Instruction.Minsd, IntrinsicType.Binary)); + Add(Intrinsic.X86Minss, new IntrinsicInfo(X86Instruction.Minss, IntrinsicType.Binary)); + Add(Intrinsic.X86Movhlps, new IntrinsicInfo(X86Instruction.Movhlps, IntrinsicType.Binary)); + Add(Intrinsic.X86Movlhps, new IntrinsicInfo(X86Instruction.Movlhps, IntrinsicType.Binary)); + Add(Intrinsic.X86Movss, new IntrinsicInfo(X86Instruction.Movss, IntrinsicType.Binary)); + Add(Intrinsic.X86Mulpd, new IntrinsicInfo(X86Instruction.Mulpd, IntrinsicType.Binary)); + Add(Intrinsic.X86Mulps, new IntrinsicInfo(X86Instruction.Mulps, IntrinsicType.Binary)); + Add(Intrinsic.X86Mulsd, new IntrinsicInfo(X86Instruction.Mulsd, IntrinsicType.Binary)); + Add(Intrinsic.X86Mulss, new IntrinsicInfo(X86Instruction.Mulss, IntrinsicType.Binary)); + Add(Intrinsic.X86Paddb, new IntrinsicInfo(X86Instruction.Paddb, IntrinsicType.Binary)); + Add(Intrinsic.X86Paddd, new IntrinsicInfo(X86Instruction.Paddd, IntrinsicType.Binary)); + Add(Intrinsic.X86Paddq, new IntrinsicInfo(X86Instruction.Paddq, IntrinsicType.Binary)); + Add(Intrinsic.X86Paddw, new IntrinsicInfo(X86Instruction.Paddw, IntrinsicType.Binary)); + Add(Intrinsic.X86Palignr, new IntrinsicInfo(X86Instruction.Palignr, IntrinsicType.TernaryImm)); + Add(Intrinsic.X86Pand, new IntrinsicInfo(X86Instruction.Pand, IntrinsicType.Binary)); + Add(Intrinsic.X86Pandn, new IntrinsicInfo(X86Instruction.Pandn, IntrinsicType.Binary)); + Add(Intrinsic.X86Pavgb, new IntrinsicInfo(X86Instruction.Pavgb, IntrinsicType.Binary)); + Add(Intrinsic.X86Pavgw, new IntrinsicInfo(X86Instruction.Pavgw, IntrinsicType.Binary)); + Add(Intrinsic.X86Pblendvb, new IntrinsicInfo(X86Instruction.Pblendvb, IntrinsicType.Ternary)); + Add(Intrinsic.X86Pclmulqdq, new IntrinsicInfo(X86Instruction.Pclmulqdq, IntrinsicType.TernaryImm)); + Add(Intrinsic.X86Pcmpeqb, new IntrinsicInfo(X86Instruction.Pcmpeqb, IntrinsicType.Binary)); + Add(Intrinsic.X86Pcmpeqd, new IntrinsicInfo(X86Instruction.Pcmpeqd, IntrinsicType.Binary)); + Add(Intrinsic.X86Pcmpeqq, new IntrinsicInfo(X86Instruction.Pcmpeqq, IntrinsicType.Binary)); + Add(Intrinsic.X86Pcmpeqw, new IntrinsicInfo(X86Instruction.Pcmpeqw, IntrinsicType.Binary)); + Add(Intrinsic.X86Pcmpgtb, new IntrinsicInfo(X86Instruction.Pcmpgtb, IntrinsicType.Binary)); + Add(Intrinsic.X86Pcmpgtd, new IntrinsicInfo(X86Instruction.Pcmpgtd, IntrinsicType.Binary)); + Add(Intrinsic.X86Pcmpgtq, new IntrinsicInfo(X86Instruction.Pcmpgtq, IntrinsicType.Binary)); + Add(Intrinsic.X86Pcmpgtw, new IntrinsicInfo(X86Instruction.Pcmpgtw, IntrinsicType.Binary)); + Add(Intrinsic.X86Pmaxsb, new IntrinsicInfo(X86Instruction.Pmaxsb, IntrinsicType.Binary)); + Add(Intrinsic.X86Pmaxsd, new IntrinsicInfo(X86Instruction.Pmaxsd, IntrinsicType.Binary)); + Add(Intrinsic.X86Pmaxsw, new IntrinsicInfo(X86Instruction.Pmaxsw, IntrinsicType.Binary)); + Add(Intrinsic.X86Pmaxub, new IntrinsicInfo(X86Instruction.Pmaxub, IntrinsicType.Binary)); + Add(Intrinsic.X86Pmaxud, new IntrinsicInfo(X86Instruction.Pmaxud, IntrinsicType.Binary)); + Add(Intrinsic.X86Pmaxuw, new IntrinsicInfo(X86Instruction.Pmaxuw, IntrinsicType.Binary)); + Add(Intrinsic.X86Pminsb, new IntrinsicInfo(X86Instruction.Pminsb, IntrinsicType.Binary)); + Add(Intrinsic.X86Pminsd, new IntrinsicInfo(X86Instruction.Pminsd, IntrinsicType.Binary)); + Add(Intrinsic.X86Pminsw, new IntrinsicInfo(X86Instruction.Pminsw, IntrinsicType.Binary)); + Add(Intrinsic.X86Pminub, new IntrinsicInfo(X86Instruction.Pminub, IntrinsicType.Binary)); + Add(Intrinsic.X86Pminud, new IntrinsicInfo(X86Instruction.Pminud, IntrinsicType.Binary)); + Add(Intrinsic.X86Pminuw, new IntrinsicInfo(X86Instruction.Pminuw, IntrinsicType.Binary)); + Add(Intrinsic.X86Pmovsxbw, new IntrinsicInfo(X86Instruction.Pmovsxbw, IntrinsicType.Unary)); + Add(Intrinsic.X86Pmovsxdq, new IntrinsicInfo(X86Instruction.Pmovsxdq, IntrinsicType.Unary)); + Add(Intrinsic.X86Pmovsxwd, new IntrinsicInfo(X86Instruction.Pmovsxwd, IntrinsicType.Unary)); + Add(Intrinsic.X86Pmovzxbw, new IntrinsicInfo(X86Instruction.Pmovzxbw, IntrinsicType.Unary)); + Add(Intrinsic.X86Pmovzxdq, new IntrinsicInfo(X86Instruction.Pmovzxdq, IntrinsicType.Unary)); + Add(Intrinsic.X86Pmovzxwd, new IntrinsicInfo(X86Instruction.Pmovzxwd, IntrinsicType.Unary)); + Add(Intrinsic.X86Pmulld, new IntrinsicInfo(X86Instruction.Pmulld, IntrinsicType.Binary)); + Add(Intrinsic.X86Pmullw, new IntrinsicInfo(X86Instruction.Pmullw, IntrinsicType.Binary)); + Add(Intrinsic.X86Popcnt, new IntrinsicInfo(X86Instruction.Popcnt, IntrinsicType.PopCount)); + Add(Intrinsic.X86Por, new IntrinsicInfo(X86Instruction.Por, IntrinsicType.Binary)); + Add(Intrinsic.X86Pshufb, new IntrinsicInfo(X86Instruction.Pshufb, IntrinsicType.Binary)); + Add(Intrinsic.X86Pshufd, new IntrinsicInfo(X86Instruction.Pshufd, IntrinsicType.BinaryImm)); + Add(Intrinsic.X86Pslld, new IntrinsicInfo(X86Instruction.Pslld, IntrinsicType.Binary)); + Add(Intrinsic.X86Pslldq, new IntrinsicInfo(X86Instruction.Pslldq, IntrinsicType.Binary)); + Add(Intrinsic.X86Psllq, new IntrinsicInfo(X86Instruction.Psllq, IntrinsicType.Binary)); + Add(Intrinsic.X86Psllw, new IntrinsicInfo(X86Instruction.Psllw, IntrinsicType.Binary)); + Add(Intrinsic.X86Psrad, new IntrinsicInfo(X86Instruction.Psrad, IntrinsicType.Binary)); + Add(Intrinsic.X86Psraw, new IntrinsicInfo(X86Instruction.Psraw, IntrinsicType.Binary)); + Add(Intrinsic.X86Psrld, new IntrinsicInfo(X86Instruction.Psrld, IntrinsicType.Binary)); + Add(Intrinsic.X86Psrlq, new IntrinsicInfo(X86Instruction.Psrlq, IntrinsicType.Binary)); + Add(Intrinsic.X86Psrldq, new IntrinsicInfo(X86Instruction.Psrldq, IntrinsicType.Binary)); + Add(Intrinsic.X86Psrlw, new IntrinsicInfo(X86Instruction.Psrlw, IntrinsicType.Binary)); + Add(Intrinsic.X86Psubb, new IntrinsicInfo(X86Instruction.Psubb, IntrinsicType.Binary)); + Add(Intrinsic.X86Psubd, new IntrinsicInfo(X86Instruction.Psubd, IntrinsicType.Binary)); + Add(Intrinsic.X86Psubq, new IntrinsicInfo(X86Instruction.Psubq, IntrinsicType.Binary)); + Add(Intrinsic.X86Psubw, new IntrinsicInfo(X86Instruction.Psubw, IntrinsicType.Binary)); + Add(Intrinsic.X86Punpckhbw, new IntrinsicInfo(X86Instruction.Punpckhbw, IntrinsicType.Binary)); + Add(Intrinsic.X86Punpckhdq, new IntrinsicInfo(X86Instruction.Punpckhdq, IntrinsicType.Binary)); + Add(Intrinsic.X86Punpckhqdq, new IntrinsicInfo(X86Instruction.Punpckhqdq, IntrinsicType.Binary)); + Add(Intrinsic.X86Punpckhwd, new IntrinsicInfo(X86Instruction.Punpckhwd, IntrinsicType.Binary)); + Add(Intrinsic.X86Punpcklbw, new IntrinsicInfo(X86Instruction.Punpcklbw, IntrinsicType.Binary)); + Add(Intrinsic.X86Punpckldq, new IntrinsicInfo(X86Instruction.Punpckldq, IntrinsicType.Binary)); + Add(Intrinsic.X86Punpcklqdq, new IntrinsicInfo(X86Instruction.Punpcklqdq, IntrinsicType.Binary)); + Add(Intrinsic.X86Punpcklwd, new IntrinsicInfo(X86Instruction.Punpcklwd, IntrinsicType.Binary)); + Add(Intrinsic.X86Pxor, new IntrinsicInfo(X86Instruction.Pxor, IntrinsicType.Binary)); + Add(Intrinsic.X86Rcpps, new IntrinsicInfo(X86Instruction.Rcpps, IntrinsicType.Unary)); + Add(Intrinsic.X86Rcpss, new IntrinsicInfo(X86Instruction.Rcpss, IntrinsicType.Unary)); + Add(Intrinsic.X86Roundpd, new IntrinsicInfo(X86Instruction.Roundpd, IntrinsicType.BinaryImm)); + Add(Intrinsic.X86Roundps, new IntrinsicInfo(X86Instruction.Roundps, IntrinsicType.BinaryImm)); + Add(Intrinsic.X86Roundsd, new IntrinsicInfo(X86Instruction.Roundsd, IntrinsicType.BinaryImm)); + Add(Intrinsic.X86Roundss, new IntrinsicInfo(X86Instruction.Roundss, IntrinsicType.BinaryImm)); + Add(Intrinsic.X86Rsqrtps, new IntrinsicInfo(X86Instruction.Rsqrtps, IntrinsicType.Unary)); + Add(Intrinsic.X86Rsqrtss, new IntrinsicInfo(X86Instruction.Rsqrtss, IntrinsicType.Unary)); + Add(Intrinsic.X86Sha256Msg1, new IntrinsicInfo(X86Instruction.Sha256Msg1, IntrinsicType.Binary)); + Add(Intrinsic.X86Sha256Msg2, new IntrinsicInfo(X86Instruction.Sha256Msg2, IntrinsicType.Binary)); + Add(Intrinsic.X86Sha256Rnds2, new IntrinsicInfo(X86Instruction.Sha256Rnds2, IntrinsicType.Ternary)); + Add(Intrinsic.X86Shufpd, new IntrinsicInfo(X86Instruction.Shufpd, IntrinsicType.TernaryImm)); + Add(Intrinsic.X86Shufps, new IntrinsicInfo(X86Instruction.Shufps, IntrinsicType.TernaryImm)); + Add(Intrinsic.X86Sqrtpd, new IntrinsicInfo(X86Instruction.Sqrtpd, IntrinsicType.Unary)); + Add(Intrinsic.X86Sqrtps, new IntrinsicInfo(X86Instruction.Sqrtps, IntrinsicType.Unary)); + Add(Intrinsic.X86Sqrtsd, new IntrinsicInfo(X86Instruction.Sqrtsd, IntrinsicType.Unary)); + Add(Intrinsic.X86Sqrtss, new IntrinsicInfo(X86Instruction.Sqrtss, IntrinsicType.Unary)); + Add(Intrinsic.X86Stmxcsr, new IntrinsicInfo(X86Instruction.None, IntrinsicType.Mxcsr)); + Add(Intrinsic.X86Subpd, new IntrinsicInfo(X86Instruction.Subpd, IntrinsicType.Binary)); + Add(Intrinsic.X86Subps, new IntrinsicInfo(X86Instruction.Subps, IntrinsicType.Binary)); + Add(Intrinsic.X86Subsd, new IntrinsicInfo(X86Instruction.Subsd, IntrinsicType.Binary)); + Add(Intrinsic.X86Subss, new IntrinsicInfo(X86Instruction.Subss, IntrinsicType.Binary)); + Add(Intrinsic.X86Unpckhpd, new IntrinsicInfo(X86Instruction.Unpckhpd, IntrinsicType.Binary)); + Add(Intrinsic.X86Unpckhps, new IntrinsicInfo(X86Instruction.Unpckhps, IntrinsicType.Binary)); + Add(Intrinsic.X86Unpcklpd, new IntrinsicInfo(X86Instruction.Unpcklpd, IntrinsicType.Binary)); + Add(Intrinsic.X86Unpcklps, new IntrinsicInfo(X86Instruction.Unpcklps, IntrinsicType.Binary)); + Add(Intrinsic.X86Vcvtph2ps, new IntrinsicInfo(X86Instruction.Vcvtph2ps, IntrinsicType.Unary)); + Add(Intrinsic.X86Vcvtps2ph, new IntrinsicInfo(X86Instruction.Vcvtps2ph, IntrinsicType.BinaryImm)); + Add(Intrinsic.X86Vfmadd231pd, new IntrinsicInfo(X86Instruction.Vfmadd231pd, IntrinsicType.Fma)); + Add(Intrinsic.X86Vfmadd231ps, new IntrinsicInfo(X86Instruction.Vfmadd231ps, IntrinsicType.Fma)); + Add(Intrinsic.X86Vfmadd231sd, new IntrinsicInfo(X86Instruction.Vfmadd231sd, IntrinsicType.Fma)); + Add(Intrinsic.X86Vfmadd231ss, new IntrinsicInfo(X86Instruction.Vfmadd231ss, IntrinsicType.Fma)); + Add(Intrinsic.X86Vfmsub231sd, new IntrinsicInfo(X86Instruction.Vfmsub231sd, IntrinsicType.Fma)); + Add(Intrinsic.X86Vfmsub231ss, new IntrinsicInfo(X86Instruction.Vfmsub231ss, IntrinsicType.Fma)); + Add(Intrinsic.X86Vfnmadd231pd, new IntrinsicInfo(X86Instruction.Vfnmadd231pd, IntrinsicType.Fma)); + Add(Intrinsic.X86Vfnmadd231ps, new IntrinsicInfo(X86Instruction.Vfnmadd231ps, IntrinsicType.Fma)); + Add(Intrinsic.X86Vfnmadd231sd, new IntrinsicInfo(X86Instruction.Vfnmadd231sd, IntrinsicType.Fma)); + Add(Intrinsic.X86Vfnmadd231ss, new IntrinsicInfo(X86Instruction.Vfnmadd231ss, IntrinsicType.Fma)); + Add(Intrinsic.X86Vfnmsub231sd, new IntrinsicInfo(X86Instruction.Vfnmsub231sd, IntrinsicType.Fma)); + Add(Intrinsic.X86Vfnmsub231ss, new IntrinsicInfo(X86Instruction.Vfnmsub231ss, IntrinsicType.Fma)); + Add(Intrinsic.X86Vpternlogd, new IntrinsicInfo(X86Instruction.Vpternlogd, IntrinsicType.TernaryImm)); + Add(Intrinsic.X86Xorpd, new IntrinsicInfo(X86Instruction.Xorpd, IntrinsicType.Binary)); + Add(Intrinsic.X86Xorps, new IntrinsicInfo(X86Instruction.Xorps, IntrinsicType.Binary)); + } + + private static void Add(Intrinsic intrin, IntrinsicInfo info) + { + _intrinTable[(int)intrin] = info; + } + + public static IntrinsicInfo GetInfo(Intrinsic intrin) + { + return _intrinTable[(int)intrin]; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/X86/IntrinsicType.cs b/src/ARMeilleure/CodeGen/X86/IntrinsicType.cs new file mode 100644 index 00000000..5a9c14af --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/IntrinsicType.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.CodeGen.X86 +{ + enum IntrinsicType + { + Comis_, + Mxcsr, + PopCount, + Unary, + UnaryToGpr, + Binary, + BinaryGpr, + BinaryImm, + Crc32, + Ternary, + TernaryImm, + Fma + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/X86/Mxcsr.cs b/src/ARMeilleure/CodeGen/X86/Mxcsr.cs new file mode 100644 index 00000000..c61eac31 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/Mxcsr.cs @@ -0,0 +1,15 @@ +using System; + +namespace ARMeilleure.CodeGen.X86 +{ + [Flags] + enum Mxcsr + { + Ftz = 1 << 15, // Flush To Zero. + Rhi = 1 << 14, // Round Mode high bit. + Rlo = 1 << 13, // Round Mode low bit. + Um = 1 << 11, // Underflow Mask. + Dm = 1 << 8, // Denormal Mask. + Daz = 1 << 6 // Denormals Are Zero. + } +} diff --git a/src/ARMeilleure/CodeGen/X86/PreAllocator.cs b/src/ARMeilleure/CodeGen/X86/PreAllocator.cs new file mode 100644 index 00000000..cb742d67 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/PreAllocator.cs @@ -0,0 +1,796 @@ +using ARMeilleure.CodeGen.RegisterAllocators; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.X86 +{ + class PreAllocator + { + public static void RunPass(CompilerContext cctx, StackAllocator stackAlloc, out int maxCallArgs) + { + maxCallArgs = -1; + + Span buffer = default; + + CallConvName callConv = CallingConvention.GetCurrentCallConv(); + + Operand[] preservedArgs = new Operand[CallingConvention.GetArgumentsOnRegsCount()]; + + for (BasicBlock block = cctx.Cfg.Blocks.First; block != null; block = block.ListNext) + { + Operation nextNode; + + for (Operation node = block.Operations.First; node != default; node = nextNode) + { + nextNode = node.ListNext; + + if (node.Instruction == Instruction.Phi) + { + continue; + } + + InsertConstantRegCopies(block.Operations, node); + InsertDestructiveRegCopies(block.Operations, node); + InsertConstrainedRegCopies(block.Operations, node); + + switch (node.Instruction) + { + case Instruction.Call: + // Get the maximum number of arguments used on a call. + // On windows, when a struct is returned from the call, + // we also need to pass the pointer where the struct + // should be written on the first argument. + int argsCount = node.SourcesCount - 1; + + if (node.Destination != default && node.Destination.Type == OperandType.V128) + { + argsCount++; + } + + if (maxCallArgs < argsCount) + { + maxCallArgs = argsCount; + } + + // Copy values to registers expected by the function + // being called, as mandated by the ABI. + if (callConv == CallConvName.Windows) + { + PreAllocatorWindows.InsertCallCopies(block.Operations, stackAlloc, node); + } + else /* if (callConv == CallConvName.SystemV) */ + { + PreAllocatorSystemV.InsertCallCopies(block.Operations, node); + } + break; + + case Instruction.ConvertToFPUI: + GenerateConvertToFPUI(block.Operations, node); + break; + + case Instruction.LoadArgument: + if (callConv == CallConvName.Windows) + { + nextNode = PreAllocatorWindows.InsertLoadArgumentCopy(cctx, ref buffer, block.Operations, preservedArgs, node); + } + else /* if (callConv == CallConvName.SystemV) */ + { + nextNode = PreAllocatorSystemV.InsertLoadArgumentCopy(cctx, ref buffer, block.Operations, preservedArgs, node); + } + break; + + case Instruction.Negate: + if (!node.GetSource(0).Type.IsInteger()) + { + GenerateNegate(block.Operations, node); + } + break; + + case Instruction.Return: + if (callConv == CallConvName.Windows) + { + PreAllocatorWindows.InsertReturnCopy(cctx, block.Operations, preservedArgs, node); + } + else /* if (callConv == CallConvName.SystemV) */ + { + PreAllocatorSystemV.InsertReturnCopy(block.Operations, node); + } + break; + + case Instruction.Tailcall: + if (callConv == CallConvName.Windows) + { + PreAllocatorWindows.InsertTailcallCopies(block.Operations, stackAlloc, node); + } + else + { + PreAllocatorSystemV.InsertTailcallCopies(block.Operations, stackAlloc, node); + } + break; + + case Instruction.VectorInsert8: + if (!HardwareCapabilities.SupportsSse41) + { + GenerateVectorInsert8(block.Operations, node); + } + break; + + case Instruction.Extended: + if (node.Intrinsic == Intrinsic.X86Ldmxcsr) + { + int stackOffset = stackAlloc.Allocate(OperandType.I32); + + node.SetSources(new Operand[] { Const(stackOffset), node.GetSource(0) }); + } + else if (node.Intrinsic == Intrinsic.X86Stmxcsr) + { + int stackOffset = stackAlloc.Allocate(OperandType.I32); + + node.SetSources(new Operand[] { Const(stackOffset) }); + } + break; + } + } + } + } + + protected static void InsertConstantRegCopies(IntrusiveList nodes, Operation node) + { + if (node.SourcesCount == 0 || IsXmmIntrinsic(node)) + { + return; + } + + Instruction inst = node.Instruction; + + Operand src1 = node.GetSource(0); + Operand src2; + + if (src1.Kind == OperandKind.Constant) + { + if (!src1.Type.IsInteger()) + { + // Handle non-integer types (FP32, FP64 and V128). + // For instructions without an immediate operand, we do the following: + // - Insert a copy with the constant value (as integer) to a GPR. + // - Insert a copy from the GPR to a XMM register. + // - Replace the constant use with the XMM register. + src1 = AddXmmCopy(nodes, node, src1); + + node.SetSource(0, src1); + } + else if (!HasConstSrc1(inst)) + { + // Handle integer types. + // Most ALU instructions accepts a 32-bits immediate on the second operand. + // We need to ensure the following: + // - If the constant is on operand 1, we need to move it. + // -- But first, we try to swap operand 1 and 2 if the instruction is commutative. + // -- Doing so may allow us to encode the constant as operand 2 and avoid a copy. + // - If the constant is on operand 2, we check if the instruction supports it, + // if not, we also add a copy. 64-bits constants are usually not supported. + if (IsCommutative(node)) + { + src2 = node.GetSource(1); + + Operand temp = src1; + + src1 = src2; + src2 = temp; + + node.SetSource(0, src1); + node.SetSource(1, src2); + } + + if (src1.Kind == OperandKind.Constant) + { + src1 = AddCopy(nodes, node, src1); + + node.SetSource(0, src1); + } + } + } + + if (node.SourcesCount < 2) + { + return; + } + + src2 = node.GetSource(1); + + if (src2.Kind == OperandKind.Constant) + { + if (!src2.Type.IsInteger()) + { + src2 = AddXmmCopy(nodes, node, src2); + + node.SetSource(1, src2); + } + else if (!HasConstSrc2(inst) || CodeGenCommon.IsLongConst(src2)) + { + src2 = AddCopy(nodes, node, src2); + + node.SetSource(1, src2); + } + } + } + + protected static void InsertConstrainedRegCopies(IntrusiveList nodes, Operation node) + { + Operand dest = node.Destination; + + switch (node.Instruction) + { + case Instruction.CompareAndSwap: + case Instruction.CompareAndSwap16: + case Instruction.CompareAndSwap8: + { + OperandType type = node.GetSource(1).Type; + + if (type == OperandType.V128) + { + // Handle the many restrictions of the compare and exchange (16 bytes) instruction: + // - The expected value should be in RDX:RAX. + // - The new value to be written should be in RCX:RBX. + // - The value at the memory location is loaded to RDX:RAX. + void SplitOperand(Operand source, Operand lr, Operand hr) + { + nodes.AddBefore(node, Operation(Instruction.VectorExtract, lr, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, hr, source, Const(1))); + } + + Operand rax = Gpr(X86Register.Rax, OperandType.I64); + Operand rbx = Gpr(X86Register.Rbx, OperandType.I64); + Operand rcx = Gpr(X86Register.Rcx, OperandType.I64); + Operand rdx = Gpr(X86Register.Rdx, OperandType.I64); + + SplitOperand(node.GetSource(1), rax, rdx); + SplitOperand(node.GetSource(2), rbx, rcx); + + Operation operation = node; + + node = nodes.AddAfter(node, Operation(Instruction.VectorCreateScalar, dest, rax)); + nodes.AddAfter(node, Operation(Instruction.VectorInsert, dest, dest, rdx, Const(1))); + + operation.SetDestinations(new Operand[] { rdx, rax }); + operation.SetSources(new Operand[] { operation.GetSource(0), rdx, rax, rcx, rbx }); + } + else + { + // Handle the many restrictions of the compare and exchange (32/64) instruction: + // - The expected value should be in (E/R)AX. + // - The value at the memory location is loaded to (E/R)AX. + Operand expected = node.GetSource(1); + Operand newValue = node.GetSource(2); + + Operand rax = Gpr(X86Register.Rax, expected.Type); + + nodes.AddBefore(node, Operation(Instruction.Copy, rax, expected)); + + // We need to store the new value into a temp, since it may + // be a constant, and this instruction does not support immediate operands. + Operand temp = Local(newValue.Type); + + nodes.AddBefore(node, Operation(Instruction.Copy, temp, newValue)); + + node.SetSources(new Operand[] { node.GetSource(0), rax, temp }); + + nodes.AddAfter(node, Operation(Instruction.Copy, dest, rax)); + + node.Destination = rax; + } + + break; + } + + case Instruction.Divide: + case Instruction.DivideUI: + { + // Handle the many restrictions of the division instructions: + // - The dividend is always in RDX:RAX. + // - The result is always in RAX. + // - Additionally it also writes the remainder in RDX. + if (dest.Type.IsInteger()) + { + Operand src1 = node.GetSource(0); + + Operand rax = Gpr(X86Register.Rax, src1.Type); + Operand rdx = Gpr(X86Register.Rdx, src1.Type); + + nodes.AddBefore(node, Operation(Instruction.Copy, rax, src1)); + nodes.AddBefore(node, Operation(Instruction.Clobber, rdx)); + + nodes.AddAfter(node, Operation(Instruction.Copy, dest, rax)); + + node.SetSources(new Operand[] { rdx, rax, node.GetSource(1) }); + node.Destination = rax; + } + + break; + } + + case Instruction.Extended: + { + bool isBlend = node.Intrinsic == Intrinsic.X86Blendvpd || + node.Intrinsic == Intrinsic.X86Blendvps || + node.Intrinsic == Intrinsic.X86Pblendvb; + + // BLENDVPD, BLENDVPS, PBLENDVB last operand is always implied to be XMM0 when VEX is not supported. + // SHA256RNDS2 always has an implied XMM0 as a last operand. + if ((isBlend && !HardwareCapabilities.SupportsVexEncoding) || node.Intrinsic == Intrinsic.X86Sha256Rnds2) + { + Operand xmm0 = Xmm(X86Register.Xmm0, OperandType.V128); + + nodes.AddBefore(node, Operation(Instruction.Copy, xmm0, node.GetSource(2))); + + node.SetSource(2, xmm0); + } + + break; + } + + case Instruction.Multiply64HighSI: + case Instruction.Multiply64HighUI: + { + // Handle the many restrictions of the i64 * i64 = i128 multiply instructions: + // - The multiplicand is always in RAX. + // - The lower 64-bits of the result is always in RAX. + // - The higher 64-bits of the result is always in RDX. + Operand src1 = node.GetSource(0); + + Operand rax = Gpr(X86Register.Rax, src1.Type); + Operand rdx = Gpr(X86Register.Rdx, src1.Type); + + nodes.AddBefore(node, Operation(Instruction.Copy, rax, src1)); + + node.SetSource(0, rax); + + nodes.AddAfter(node, Operation(Instruction.Copy, dest, rdx)); + + node.SetDestinations(new Operand[] { rdx, rax }); + + break; + } + + case Instruction.RotateRight: + case Instruction.ShiftLeft: + case Instruction.ShiftRightSI: + case Instruction.ShiftRightUI: + { + // The shift register is always implied to be CL (low 8-bits of RCX or ECX). + if (node.GetSource(1).Kind == OperandKind.LocalVariable) + { + Operand rcx = Gpr(X86Register.Rcx, OperandType.I32); + + nodes.AddBefore(node, Operation(Instruction.Copy, rcx, node.GetSource(1))); + + node.SetSource(1, rcx); + } + + break; + } + } + } + + protected static void InsertDestructiveRegCopies(IntrusiveList nodes, Operation node) + { + if (node.Destination == default || node.SourcesCount == 0) + { + return; + } + + Instruction inst = node.Instruction; + + Operand dest = node.Destination; + Operand src1 = node.GetSource(0); + + // The multiply instruction (that maps to IMUL) is somewhat special, it has + // a three operand form where the second source is a immediate value. + bool threeOperandForm = inst == Instruction.Multiply && node.GetSource(1).Kind == OperandKind.Constant; + + if (IsSameOperandDestSrc1(node) && src1.Kind == OperandKind.LocalVariable && !threeOperandForm) + { + bool useNewLocal = false; + + for (int srcIndex = 1; srcIndex < node.SourcesCount; srcIndex++) + { + if (node.GetSource(srcIndex) == dest) + { + useNewLocal = true; + + break; + } + } + + if (useNewLocal) + { + // Dest is being used as some source already, we need to use a new + // local to store the temporary value, otherwise the value on dest + // local would be overwritten. + Operand temp = Local(dest.Type); + + nodes.AddBefore(node, Operation(Instruction.Copy, temp, src1)); + + node.SetSource(0, temp); + + nodes.AddAfter(node, Operation(Instruction.Copy, dest, temp)); + + node.Destination = temp; + } + else + { + nodes.AddBefore(node, Operation(Instruction.Copy, dest, src1)); + + node.SetSource(0, dest); + } + } + else if (inst == Instruction.ConditionalSelect) + { + Operand src2 = node.GetSource(1); + Operand src3 = node.GetSource(2); + + if (src1 == dest || src2 == dest) + { + Operand temp = Local(dest.Type); + + nodes.AddBefore(node, Operation(Instruction.Copy, temp, src3)); + + node.SetSource(2, temp); + + nodes.AddAfter(node, Operation(Instruction.Copy, dest, temp)); + + node.Destination = temp; + } + else + { + nodes.AddBefore(node, Operation(Instruction.Copy, dest, src3)); + + node.SetSource(2, dest); + } + } + } + + private static void GenerateConvertToFPUI(IntrusiveList nodes, Operation node) + { + // Unsigned integer to FP conversions are not supported on X86. + // We need to turn them into signed integer to FP conversions, and + // adjust the final result. + Operand dest = node.Destination; + Operand source = node.GetSource(0); + + Debug.Assert(source.Type.IsInteger(), $"Invalid source type \"{source.Type}\"."); + + Operation currentNode = node; + + if (source.Type == OperandType.I32) + { + // For 32-bits integers, we can just zero-extend to 64-bits, + // and then use the 64-bits signed conversion instructions. + Operand zex = Local(OperandType.I64); + + node = nodes.AddAfter(node, Operation(Instruction.ZeroExtend32, zex, source)); + node = nodes.AddAfter(node, Operation(Instruction.ConvertToFP, dest, zex)); + } + else /* if (source.Type == OperandType.I64) */ + { + // For 64-bits integers, we need to do the following: + // - Ensure that the integer has the most significant bit clear. + // -- This can be done by shifting the value right by 1, that is, dividing by 2. + // -- The least significant bit is lost in this case though. + // - We can then convert the shifted value with a signed integer instruction. + // - The result still needs to be corrected after that. + // -- First, we need to multiply the result by 2, as we divided it by 2 before. + // --- This can be done efficiently by adding the result to itself. + // -- Then, we need to add the least significant bit that was shifted out. + // --- We can convert the least significant bit to float, and add it to the result. + Operand lsb = Local(OperandType.I64); + Operand half = Local(OperandType.I64); + + Operand lsbF = Local(dest.Type); + + node = nodes.AddAfter(node, Operation(Instruction.Copy, lsb, source)); + node = nodes.AddAfter(node, Operation(Instruction.Copy, half, source)); + + node = nodes.AddAfter(node, Operation(Instruction.BitwiseAnd, lsb, lsb, Const(1L))); + node = nodes.AddAfter(node, Operation(Instruction.ShiftRightUI, half, half, Const(1))); + + node = nodes.AddAfter(node, Operation(Instruction.ConvertToFP, lsbF, lsb)); + node = nodes.AddAfter(node, Operation(Instruction.ConvertToFP, dest, half)); + + node = nodes.AddAfter(node, Operation(Instruction.Add, dest, dest, dest)); + nodes.AddAfter(node, Operation(Instruction.Add, dest, dest, lsbF)); + } + + Delete(nodes, currentNode); + } + + private static void GenerateNegate(IntrusiveList nodes, Operation node) + { + // There's no SSE FP negate instruction, so we need to transform that into + // a XOR of the value to be negated with a mask with the highest bit set. + // This also produces -0 for a negation of the value 0. + Operand dest = node.Destination; + Operand source = node.GetSource(0); + + Debug.Assert(dest.Type == OperandType.FP32 || + dest.Type == OperandType.FP64, $"Invalid destination type \"{dest.Type}\"."); + + Operation currentNode = node; + + Operand res = Local(dest.Type); + + node = nodes.AddAfter(node, Operation(Instruction.VectorOne, res)); + + if (dest.Type == OperandType.FP32) + { + node = nodes.AddAfter(node, Operation(Intrinsic.X86Pslld, res, res, Const(31))); + } + else /* if (dest.Type == OperandType.FP64) */ + { + node = nodes.AddAfter(node, Operation(Intrinsic.X86Psllq, res, res, Const(63))); + } + + node = nodes.AddAfter(node, Operation(Intrinsic.X86Xorps, res, res, source)); + + nodes.AddAfter(node, Operation(Instruction.Copy, dest, res)); + + Delete(nodes, currentNode); + } + + private static void GenerateVectorInsert8(IntrusiveList nodes, Operation node) + { + // Handle vector insertion, when SSE 4.1 is not supported. + Operand dest = node.Destination; + Operand src1 = node.GetSource(0); // Vector + Operand src2 = node.GetSource(1); // Value + Operand src3 = node.GetSource(2); // Index + + Debug.Assert(src3.Kind == OperandKind.Constant); + + byte index = src3.AsByte(); + + Debug.Assert(index < 16); + + Operation currentNode = node; + + Operand temp1 = Local(OperandType.I32); + Operand temp2 = Local(OperandType.I32); + + node = nodes.AddAfter(node, Operation(Instruction.Copy, temp2, src2)); + + Operation vextOp = Operation(Instruction.VectorExtract16, temp1, src1, Const(index >> 1)); + + node = nodes.AddAfter(node, vextOp); + + if ((index & 1) != 0) + { + node = nodes.AddAfter(node, Operation(Instruction.ZeroExtend8, temp1, temp1)); + node = nodes.AddAfter(node, Operation(Instruction.ShiftLeft, temp2, temp2, Const(8))); + node = nodes.AddAfter(node, Operation(Instruction.BitwiseOr, temp1, temp1, temp2)); + } + else + { + node = nodes.AddAfter(node, Operation(Instruction.ZeroExtend8, temp2, temp2)); + node = nodes.AddAfter(node, Operation(Instruction.BitwiseAnd, temp1, temp1, Const(0xff00))); + node = nodes.AddAfter(node, Operation(Instruction.BitwiseOr, temp1, temp1, temp2)); + } + + Operation vinsOp = Operation(Instruction.VectorInsert16, dest, src1, temp1, Const(index >> 1)); + + nodes.AddAfter(node, vinsOp); + + Delete(nodes, currentNode); + } + + protected static Operand AddXmmCopy(IntrusiveList nodes, Operation node, Operand source) + { + Operand temp = Local(source.Type); + Operand intConst = AddCopy(nodes, node, GetIntConst(source)); + + Operation copyOp = Operation(Instruction.VectorCreateScalar, temp, intConst); + + nodes.AddBefore(node, copyOp); + + return temp; + } + + protected static Operand AddCopy(IntrusiveList nodes, Operation node, Operand source) + { + Operand temp = Local(source.Type); + + Operation copyOp = Operation(Instruction.Copy, temp, source); + + nodes.AddBefore(node, copyOp); + + return temp; + } + + private static Operand GetIntConst(Operand value) + { + if (value.Type == OperandType.FP32) + { + return Const(value.AsInt32()); + } + else if (value.Type == OperandType.FP64) + { + return Const(value.AsInt64()); + } + + return value; + } + + protected static void Delete(IntrusiveList nodes, Operation node) + { + node.Destination = default; + + for (int index = 0; index < node.SourcesCount; index++) + { + node.SetSource(index, default); + } + + nodes.Remove(node); + } + + protected static Operand Gpr(X86Register register, OperandType type) + { + return Register((int)register, RegisterType.Integer, type); + } + + protected static Operand Xmm(X86Register register, OperandType type) + { + return Register((int)register, RegisterType.Vector, type); + } + + private static bool IsSameOperandDestSrc1(Operation operation) + { + switch (operation.Instruction) + { + case Instruction.Add: + return !HardwareCapabilities.SupportsVexEncoding && !operation.Destination.Type.IsInteger(); + case Instruction.Multiply: + case Instruction.Subtract: + return !HardwareCapabilities.SupportsVexEncoding || operation.Destination.Type.IsInteger(); + + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseNot: + case Instruction.BitwiseOr: + case Instruction.ByteSwap: + case Instruction.Negate: + case Instruction.RotateRight: + case Instruction.ShiftLeft: + case Instruction.ShiftRightSI: + case Instruction.ShiftRightUI: + return true; + + case Instruction.Divide: + return !HardwareCapabilities.SupportsVexEncoding && !operation.Destination.Type.IsInteger(); + + case Instruction.VectorInsert: + case Instruction.VectorInsert16: + case Instruction.VectorInsert8: + return !HardwareCapabilities.SupportsVexEncoding; + + case Instruction.Extended: + return IsIntrinsicSameOperandDestSrc1(operation); + } + + return IsVexSameOperandDestSrc1(operation); + } + + private static bool IsIntrinsicSameOperandDestSrc1(Operation operation) + { + IntrinsicInfo info = IntrinsicTable.GetInfo(operation.Intrinsic); + + return info.Type == IntrinsicType.Crc32 || info.Type == IntrinsicType.Fma || IsVexSameOperandDestSrc1(operation); + } + + private static bool IsVexSameOperandDestSrc1(Operation operation) + { + if (IsIntrinsic(operation.Instruction)) + { + IntrinsicInfo info = IntrinsicTable.GetInfo(operation.Intrinsic); + + bool hasVex = HardwareCapabilities.SupportsVexEncoding && Assembler.SupportsVexPrefix(info.Inst); + + bool isUnary = operation.SourcesCount < 2; + + bool hasVecDest = operation.Destination != default && operation.Destination.Type == OperandType.V128; + + return !hasVex && !isUnary && hasVecDest; + } + + return false; + } + + private static bool HasConstSrc1(Instruction inst) + { + switch (inst) + { + case Instruction.Copy: + case Instruction.LoadArgument: + case Instruction.Spill: + case Instruction.SpillArg: + return true; + } + + return false; + } + + private static bool HasConstSrc2(Instruction inst) + { + switch (inst) + { + case Instruction.Add: + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseOr: + case Instruction.BranchIf: + case Instruction.Compare: + case Instruction.Multiply: + case Instruction.RotateRight: + case Instruction.ShiftLeft: + case Instruction.ShiftRightSI: + case Instruction.ShiftRightUI: + case Instruction.Store: + case Instruction.Store16: + case Instruction.Store8: + case Instruction.Subtract: + case Instruction.VectorExtract: + case Instruction.VectorExtract16: + case Instruction.VectorExtract8: + return true; + } + + return false; + } + + private static bool IsCommutative(Operation operation) + { + switch (operation.Instruction) + { + case Instruction.Add: + case Instruction.BitwiseAnd: + case Instruction.BitwiseExclusiveOr: + case Instruction.BitwiseOr: + case Instruction.Multiply: + return true; + + case Instruction.BranchIf: + case Instruction.Compare: + { + Operand comp = operation.GetSource(2); + + Debug.Assert(comp.Kind == OperandKind.Constant); + + var compType = (Comparison)comp.AsInt32(); + + return compType == Comparison.Equal || compType == Comparison.NotEqual; + } + } + + return false; + } + + private static bool IsIntrinsic(Instruction inst) + { + return inst == Instruction.Extended; + } + + private static bool IsXmmIntrinsic(Operation operation) + { + if (operation.Instruction != Instruction.Extended) + { + return false; + } + + IntrinsicInfo info = IntrinsicTable.GetInfo(operation.Intrinsic); + + return info.Type != IntrinsicType.Crc32; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/X86/PreAllocatorSystemV.cs b/src/ARMeilleure/CodeGen/X86/PreAllocatorSystemV.cs new file mode 100644 index 00000000..a84d5050 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/PreAllocatorSystemV.cs @@ -0,0 +1,334 @@ +using ARMeilleure.CodeGen.RegisterAllocators; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.X86 +{ + class PreAllocatorSystemV : PreAllocator + { + public static void InsertCallCopies(IntrusiveList nodes, Operation node) + { + Operand dest = node.Destination; + + List sources = new List + { + node.GetSource(0) + }; + + int argsCount = node.SourcesCount - 1; + + int intMax = CallingConvention.GetIntArgumentsOnRegsCount(); + int vecMax = CallingConvention.GetVecArgumentsOnRegsCount(); + + int intCount = 0; + int vecCount = 0; + + int stackOffset = 0; + + for (int index = 0; index < argsCount; index++) + { + Operand source = node.GetSource(index + 1); + + bool passOnReg; + + if (source.Type.IsInteger()) + { + passOnReg = intCount < intMax; + } + else if (source.Type == OperandType.V128) + { + passOnReg = intCount + 1 < intMax; + } + else + { + passOnReg = vecCount < vecMax; + } + + if (source.Type == OperandType.V128 && passOnReg) + { + // V128 is a struct, we pass each half on a GPR if possible. + Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1))); + + continue; + } + + if (passOnReg) + { + Operand argReg = source.Type.IsInteger() + ? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type) + : Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type); + + Operation copyOp = Operation(Instruction.Copy, argReg, source); + + InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp)); + + sources.Add(argReg); + } + else + { + Operand offset = Const(stackOffset); + + Operation spillOp = Operation(Instruction.SpillArg, default, offset, source); + + InsertConstantRegCopies(nodes, nodes.AddBefore(node, spillOp)); + + stackOffset += source.Type.GetSizeInBytes(); + } + } + + node.SetSources(sources.ToArray()); + + if (dest != default) + { + if (dest.Type == OperandType.V128) + { + Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64); + Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64); + + Operation operation = node; + + node = nodes.AddAfter(node, Operation(Instruction.VectorCreateScalar, dest, retLReg)); + nodes.AddAfter(node, Operation(Instruction.VectorInsert, dest, dest, retHReg, Const(1))); + + operation.Destination = default; + } + else + { + Operand retReg = dest.Type.IsInteger() + ? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type) + : Xmm(CallingConvention.GetVecReturnRegister(), dest.Type); + + Operation copyOp = Operation(Instruction.Copy, dest, retReg); + + nodes.AddAfter(node, copyOp); + + node.Destination = retReg; + } + } + } + + public static void InsertTailcallCopies(IntrusiveList nodes, StackAllocator stackAlloc, Operation node) + { + List sources = new List + { + node.GetSource(0) + }; + + int argsCount = node.SourcesCount - 1; + + int intMax = CallingConvention.GetIntArgumentsOnRegsCount(); + int vecMax = CallingConvention.GetVecArgumentsOnRegsCount(); + + int intCount = 0; + int vecCount = 0; + + // Handle arguments passed on registers. + for (int index = 0; index < argsCount; index++) + { + Operand source = node.GetSource(1 + index); + + bool passOnReg; + + if (source.Type.IsInteger()) + { + passOnReg = intCount + 1 < intMax; + } + else + { + passOnReg = vecCount < vecMax; + } + + if (source.Type == OperandType.V128 && passOnReg) + { + // V128 is a struct, we pass each half on a GPR if possible. + Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64); + + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1))); + + continue; + } + + if (passOnReg) + { + Operand argReg = source.Type.IsInteger() + ? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type) + : Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type); + + Operation copyOp = Operation(Instruction.Copy, argReg, source); + + InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp)); + + sources.Add(argReg); + } + else + { + throw new NotImplementedException("Spilling is not currently supported for tail calls. (too many arguments)"); + } + } + + // The target address must be on the return registers, since we + // don't return anything and it is guaranteed to not be a + // callee saved register (which would be trashed on the epilogue). + Operand retReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64); + + Operation addrCopyOp = Operation(Instruction.Copy, retReg, node.GetSource(0)); + + nodes.AddBefore(node, addrCopyOp); + + sources[0] = retReg; + + node.SetSources(sources.ToArray()); + } + + public static Operation InsertLoadArgumentCopy( + CompilerContext cctx, + ref Span buffer, + IntrusiveList nodes, + Operand[] preservedArgs, + Operation node) + { + Operand source = node.GetSource(0); + + Debug.Assert(source.Kind == OperandKind.Constant, "Non-constant LoadArgument source kind."); + + int index = source.AsInt32(); + + int intCount = 0; + int vecCount = 0; + + for (int cIndex = 0; cIndex < index; cIndex++) + { + OperandType argType = cctx.FuncArgTypes[cIndex]; + + if (argType.IsInteger()) + { + intCount++; + } + else if (argType == OperandType.V128) + { + intCount += 2; + } + else + { + vecCount++; + } + } + + bool passOnReg; + + if (source.Type.IsInteger()) + { + passOnReg = intCount < CallingConvention.GetIntArgumentsOnRegsCount(); + } + else if (source.Type == OperandType.V128) + { + passOnReg = intCount + 1 < CallingConvention.GetIntArgumentsOnRegsCount(); + } + else + { + passOnReg = vecCount < CallingConvention.GetVecArgumentsOnRegsCount(); + } + + if (passOnReg) + { + Operand dest = node.Destination; + + if (preservedArgs[index] == default) + { + if (dest.Type == OperandType.V128) + { + // V128 is a struct, we pass each half on a GPR if possible. + Operand pArg = Local(OperandType.V128); + + Operand argLReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount), OperandType.I64); + Operand argHReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount + 1), OperandType.I64); + + Operation copyL = Operation(Instruction.VectorCreateScalar, pArg, argLReg); + Operation copyH = Operation(Instruction.VectorInsert, pArg, pArg, argHReg, Const(1)); + + cctx.Cfg.Entry.Operations.AddFirst(copyH); + cctx.Cfg.Entry.Operations.AddFirst(copyL); + + preservedArgs[index] = pArg; + } + else + { + Operand pArg = Local(dest.Type); + + Operand argReg = dest.Type.IsInteger() + ? Gpr(CallingConvention.GetIntArgumentRegister(intCount), dest.Type) + : Xmm(CallingConvention.GetVecArgumentRegister(vecCount), dest.Type); + + Operation copyOp = Operation(Instruction.Copy, pArg, argReg); + + cctx.Cfg.Entry.Operations.AddFirst(copyOp); + + preservedArgs[index] = pArg; + } + } + + Operation nextNode; + + if (dest.AssignmentsCount == 1) + { + // Let's propagate the argument if we can to avoid copies. + PreAllocatorCommon.Propagate(ref buffer, dest, preservedArgs[index]); + nextNode = node.ListNext; + } + else + { + Operation argCopyOp = Operation(Instruction.Copy, dest, preservedArgs[index]); + nextNode = nodes.AddBefore(node, argCopyOp); + } + + Delete(nodes, node); + return nextNode; + } + else + { + // TODO: Pass on stack. + return node; + } + } + + public static void InsertReturnCopy(IntrusiveList nodes, Operation node) + { + if (node.SourcesCount == 0) + { + return; + } + + Operand source = node.GetSource(0); + + if (source.Type == OperandType.V128) + { + Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64); + Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64); + + nodes.AddBefore(node, Operation(Instruction.VectorExtract, retLReg, source, Const(0))); + nodes.AddBefore(node, Operation(Instruction.VectorExtract, retHReg, source, Const(1))); + } + else + { + Operand retReg = source.Type.IsInteger() + ? Gpr(CallingConvention.GetIntReturnRegister(), source.Type) + : Xmm(CallingConvention.GetVecReturnRegister(), source.Type); + + Operation retCopyOp = Operation(Instruction.Copy, retReg, source); + + nodes.AddBefore(node, retCopyOp); + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/X86/PreAllocatorWindows.cs b/src/ARMeilleure/CodeGen/X86/PreAllocatorWindows.cs new file mode 100644 index 00000000..45319e6a --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/PreAllocatorWindows.cs @@ -0,0 +1,327 @@ +using ARMeilleure.CodeGen.RegisterAllocators; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.X86 +{ + class PreAllocatorWindows : PreAllocator + { + public static void InsertCallCopies(IntrusiveList nodes, StackAllocator stackAlloc, Operation node) + { + Operand dest = node.Destination; + + // Handle struct arguments. + int retArgs = 0; + int stackAllocOffset = 0; + + int AllocateOnStack(int size) + { + // We assume that the stack allocator is initially empty (TotalSize = 0). + // Taking that into account, we can reuse the space allocated for other + // calls by keeping track of our own allocated size (stackAllocOffset). + // If the space allocated is not big enough, then we just expand it. + int offset = stackAllocOffset; + + if (stackAllocOffset + size > stackAlloc.TotalSize) + { + stackAlloc.Allocate((stackAllocOffset + size) - stackAlloc.TotalSize); + } + + stackAllocOffset += size; + + return offset; + } + + Operand arg0Reg = default; + + if (dest != default && dest.Type == OperandType.V128) + { + int stackOffset = AllocateOnStack(dest.Type.GetSizeInBytes()); + + arg0Reg = Gpr(CallingConvention.GetIntArgumentRegister(0), OperandType.I64); + + Operation allocOp = Operation(Instruction.StackAlloc, arg0Reg, Const(stackOffset)); + + nodes.AddBefore(node, allocOp); + + retArgs = 1; + } + + int argsCount = node.SourcesCount - 1; + int maxArgs = CallingConvention.GetArgumentsOnRegsCount() - retArgs; + + if (argsCount > maxArgs) + { + argsCount = maxArgs; + } + + Operand[] sources = new Operand[1 + retArgs + argsCount]; + + sources[0] = node.GetSource(0); + + if (arg0Reg != default) + { + sources[1] = arg0Reg; + } + + for (int index = 1; index < node.SourcesCount; index++) + { + Operand source = node.GetSource(index); + + if (source.Type == OperandType.V128) + { + Operand stackAddr = Local(OperandType.I64); + + int stackOffset = AllocateOnStack(source.Type.GetSizeInBytes()); + + nodes.AddBefore(node, Operation(Instruction.StackAlloc, stackAddr, Const(stackOffset))); + + Operation storeOp = Operation(Instruction.Store, default, stackAddr, source); + + InsertConstantRegCopies(nodes, nodes.AddBefore(node, storeOp)); + + node.SetSource(index, stackAddr); + } + } + + // Handle arguments passed on registers. + for (int index = 0; index < argsCount; index++) + { + Operand source = node.GetSource(index + 1); + Operand argReg; + + int argIndex = index + retArgs; + + if (source.Type.IsInteger()) + { + argReg = Gpr(CallingConvention.GetIntArgumentRegister(argIndex), source.Type); + } + else + { + argReg = Xmm(CallingConvention.GetVecArgumentRegister(argIndex), source.Type); + } + + Operation copyOp = Operation(Instruction.Copy, argReg, source); + + InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp)); + + sources[1 + retArgs + index] = argReg; + } + + // The remaining arguments (those that are not passed on registers) + // should be passed on the stack, we write them to the stack with "SpillArg". + for (int index = argsCount; index < node.SourcesCount - 1; index++) + { + Operand source = node.GetSource(index + 1); + Operand offset = Const((index + retArgs) * 8); + + Operation spillOp = Operation(Instruction.SpillArg, default, offset, source); + + InsertConstantRegCopies(nodes, nodes.AddBefore(node, spillOp)); + } + + if (dest != default) + { + if (dest.Type == OperandType.V128) + { + Operand retValueAddr = Local(OperandType.I64); + + nodes.AddBefore(node, Operation(Instruction.Copy, retValueAddr, arg0Reg)); + + Operation loadOp = Operation(Instruction.Load, dest, retValueAddr); + + nodes.AddAfter(node, loadOp); + + node.Destination = default; + } + else + { + Operand retReg = dest.Type.IsInteger() + ? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type) + : Xmm(CallingConvention.GetVecReturnRegister(), dest.Type); + + Operation copyOp = Operation(Instruction.Copy, dest, retReg); + + nodes.AddAfter(node, copyOp); + + node.Destination = retReg; + } + } + + node.SetSources(sources); + } + + public static void InsertTailcallCopies(IntrusiveList nodes, StackAllocator stackAlloc, Operation node) + { + int argsCount = node.SourcesCount - 1; + int maxArgs = CallingConvention.GetArgumentsOnRegsCount(); + + if (argsCount > maxArgs) + { + throw new NotImplementedException("Spilling is not currently supported for tail calls. (too many arguments)"); + } + + Operand[] sources = new Operand[1 + argsCount]; + + // Handle arguments passed on registers. + for (int index = 0; index < argsCount; index++) + { + Operand source = node.GetSource(1 + index); + Operand argReg = source.Type.IsInteger() + ? Gpr(CallingConvention.GetIntArgumentRegister(index), source.Type) + : Xmm(CallingConvention.GetVecArgumentRegister(index), source.Type); + + Operation copyOp = Operation(Instruction.Copy, argReg, source); + + InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp)); + + sources[1 + index] = argReg; + } + + // The target address must be on the return registers, since we + // don't return anything and it is guaranteed to not be a + // callee saved register (which would be trashed on the epilogue). + Operand retReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64); + + Operation addrCopyOp = Operation(Instruction.Copy, retReg, node.GetSource(0)); + + nodes.AddBefore(node, addrCopyOp); + + sources[0] = retReg; + + node.SetSources(sources); + } + + public static Operation InsertLoadArgumentCopy( + CompilerContext cctx, + ref Span buffer, + IntrusiveList nodes, + Operand[] preservedArgs, + Operation node) + { + Operand source = node.GetSource(0); + + Debug.Assert(source.Kind == OperandKind.Constant, "Non-constant LoadArgument source kind."); + + int retArgs = cctx.FuncReturnType == OperandType.V128 ? 1 : 0; + + int index = source.AsInt32() + retArgs; + + if (index < CallingConvention.GetArgumentsOnRegsCount()) + { + Operand dest = node.Destination; + + if (preservedArgs[index] == default) + { + Operand argReg, pArg; + + if (dest.Type.IsInteger()) + { + argReg = Gpr(CallingConvention.GetIntArgumentRegister(index), dest.Type); + pArg = Local(dest.Type); + } + else if (dest.Type == OperandType.V128) + { + argReg = Gpr(CallingConvention.GetIntArgumentRegister(index), OperandType.I64); + pArg = Local(OperandType.I64); + } + else + { + argReg = Xmm(CallingConvention.GetVecArgumentRegister(index), dest.Type); + pArg = Local(dest.Type); + } + + Operation copyOp = Operation(Instruction.Copy, pArg, argReg); + + cctx.Cfg.Entry.Operations.AddFirst(copyOp); + + preservedArgs[index] = pArg; + } + + Operation nextNode; + + if (dest.Type != OperandType.V128 && dest.AssignmentsCount == 1) + { + // Let's propagate the argument if we can to avoid copies. + PreAllocatorCommon.Propagate(ref buffer, dest, preservedArgs[index]); + nextNode = node.ListNext; + } + else + { + Operation argCopyOp = Operation(dest.Type == OperandType.V128 + ? Instruction.Load + : Instruction.Copy, dest, preservedArgs[index]); + + nextNode = nodes.AddBefore(node, argCopyOp); + } + + Delete(nodes, node); + return nextNode; + } + else + { + // TODO: Pass on stack. + return node; + } + } + + public static void InsertReturnCopy( + CompilerContext cctx, + IntrusiveList nodes, + Operand[] preservedArgs, + Operation node) + { + if (node.SourcesCount == 0) + { + return; + } + + Operand source = node.GetSource(0); + Operand retReg; + + if (source.Type.IsInteger()) + { + retReg = Gpr(CallingConvention.GetIntReturnRegister(), source.Type); + } + else if (source.Type == OperandType.V128) + { + if (preservedArgs[0] == default) + { + Operand preservedArg = Local(OperandType.I64); + Operand arg0 = Gpr(CallingConvention.GetIntArgumentRegister(0), OperandType.I64); + + Operation copyOp = Operation(Instruction.Copy, preservedArg, arg0); + + cctx.Cfg.Entry.Operations.AddFirst(copyOp); + + preservedArgs[0] = preservedArg; + } + + retReg = preservedArgs[0]; + } + else + { + retReg = Xmm(CallingConvention.GetVecReturnRegister(), source.Type); + } + + if (source.Type == OperandType.V128) + { + Operation retStoreOp = Operation(Instruction.Store, default, retReg, source); + + nodes.AddBefore(node, retStoreOp); + } + else + { + Operation retCopyOp = Operation(Instruction.Copy, retReg, source); + + nodes.AddBefore(node, retCopyOp); + } + + node.SetSources(Array.Empty()); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/X86/X86Condition.cs b/src/ARMeilleure/CodeGen/X86/X86Condition.cs new file mode 100644 index 00000000..c82cbdec --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/X86Condition.cs @@ -0,0 +1,47 @@ +using ARMeilleure.IntermediateRepresentation; +using System; + +namespace ARMeilleure.CodeGen.X86 +{ + enum X86Condition + { + Overflow = 0x0, + NotOverflow = 0x1, + Below = 0x2, + AboveOrEqual = 0x3, + Equal = 0x4, + NotEqual = 0x5, + BelowOrEqual = 0x6, + Above = 0x7, + Sign = 0x8, + NotSign = 0x9, + ParityEven = 0xa, + ParityOdd = 0xb, + Less = 0xc, + GreaterOrEqual = 0xd, + LessOrEqual = 0xe, + Greater = 0xf + } + + static class ComparisonX86Extensions + { + public static X86Condition ToX86Condition(this Comparison comp) + { + return comp switch + { + Comparison.Equal => X86Condition.Equal, + Comparison.NotEqual => X86Condition.NotEqual, + Comparison.Greater => X86Condition.Greater, + Comparison.LessOrEqual => X86Condition.LessOrEqual, + Comparison.GreaterUI => X86Condition.Above, + Comparison.LessOrEqualUI => X86Condition.BelowOrEqual, + Comparison.GreaterOrEqual => X86Condition.GreaterOrEqual, + Comparison.Less => X86Condition.Less, + Comparison.GreaterOrEqualUI => X86Condition.AboveOrEqual, + Comparison.LessUI => X86Condition.Below, + + _ => throw new ArgumentException(null, nameof(comp)) + }; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/X86/X86Instruction.cs b/src/ARMeilleure/CodeGen/X86/X86Instruction.cs new file mode 100644 index 00000000..9a85c516 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/X86Instruction.cs @@ -0,0 +1,231 @@ +namespace ARMeilleure.CodeGen.X86 +{ + enum X86Instruction + { + None, + Add, + Addpd, + Addps, + Addsd, + Addss, + Aesdec, + Aesdeclast, + Aesenc, + Aesenclast, + Aesimc, + And, + Andnpd, + Andnps, + Andpd, + Andps, + Blendvpd, + Blendvps, + Bsr, + Bswap, + Call, + Cmovcc, + Cmp, + Cmppd, + Cmpps, + Cmpsd, + Cmpss, + Cmpxchg, + Cmpxchg16b, + Cmpxchg8, + Comisd, + Comiss, + Crc32, + Crc32_16, + Crc32_8, + Cvtdq2pd, + Cvtdq2ps, + Cvtpd2dq, + Cvtpd2ps, + Cvtps2dq, + Cvtps2pd, + Cvtsd2si, + Cvtsd2ss, + Cvtsi2sd, + Cvtsi2ss, + Cvtss2sd, + Cvtss2si, + Div, + Divpd, + Divps, + Divsd, + Divss, + Gf2p8affineqb, + Haddpd, + Haddps, + Idiv, + Imul, + Imul128, + Insertps, + Jmp, + Ldmxcsr, + Lea, + Maxpd, + Maxps, + Maxsd, + Maxss, + Minpd, + Minps, + Minsd, + Minss, + Mov, + Mov16, + Mov8, + Movd, + Movdqu, + Movhlps, + Movlhps, + Movq, + Movsd, + Movss, + Movsx16, + Movsx32, + Movsx8, + Movzx16, + Movzx8, + Mul128, + Mulpd, + Mulps, + Mulsd, + Mulss, + Neg, + Not, + Or, + Paddb, + Paddd, + Paddq, + Paddw, + Palignr, + Pand, + Pandn, + Pavgb, + Pavgw, + Pblendvb, + Pclmulqdq, + Pcmpeqb, + Pcmpeqd, + Pcmpeqq, + Pcmpeqw, + Pcmpgtb, + Pcmpgtd, + Pcmpgtq, + Pcmpgtw, + Pextrb, + Pextrd, + Pextrq, + Pextrw, + Pinsrb, + Pinsrd, + Pinsrq, + Pinsrw, + Pmaxsb, + Pmaxsd, + Pmaxsw, + Pmaxub, + Pmaxud, + Pmaxuw, + Pminsb, + Pminsd, + Pminsw, + Pminub, + Pminud, + Pminuw, + Pmovsxbw, + Pmovsxdq, + Pmovsxwd, + Pmovzxbw, + Pmovzxdq, + Pmovzxwd, + Pmulld, + Pmullw, + Pop, + Popcnt, + Por, + Pshufb, + Pshufd, + Pslld, + Pslldq, + Psllq, + Psllw, + Psrad, + Psraw, + Psrld, + Psrlq, + Psrldq, + Psrlw, + Psubb, + Psubd, + Psubq, + Psubw, + Punpckhbw, + Punpckhdq, + Punpckhqdq, + Punpckhwd, + Punpcklbw, + Punpckldq, + Punpcklqdq, + Punpcklwd, + Push, + Pxor, + Rcpps, + Rcpss, + Ror, + Roundpd, + Roundps, + Roundsd, + Roundss, + Rsqrtps, + Rsqrtss, + Sar, + Setcc, + Sha256Msg1, + Sha256Msg2, + Sha256Rnds2, + Shl, + Shr, + Shufpd, + Shufps, + Sqrtpd, + Sqrtps, + Sqrtsd, + Sqrtss, + Stmxcsr, + Sub, + Subpd, + Subps, + Subsd, + Subss, + Test, + Unpckhpd, + Unpckhps, + Unpcklpd, + Unpcklps, + Vblendvpd, + Vblendvps, + Vcvtph2ps, + Vcvtps2ph, + Vfmadd231pd, + Vfmadd231ps, + Vfmadd231sd, + Vfmadd231ss, + Vfmsub231sd, + Vfmsub231ss, + Vfnmadd231pd, + Vfnmadd231ps, + Vfnmadd231sd, + Vfnmadd231ss, + Vfnmsub231sd, + Vfnmsub231ss, + Vpblendvb, + Vpternlogd, + Xor, + Xorpd, + Xorps, + + Count + } +} \ No newline at end of file diff --git a/src/ARMeilleure/CodeGen/X86/X86Optimizer.cs b/src/ARMeilleure/CodeGen/X86/X86Optimizer.cs new file mode 100644 index 00000000..98a19b9a --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/X86Optimizer.cs @@ -0,0 +1,259 @@ +using ARMeilleure.CodeGen.Optimizations; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System.Collections.Generic; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.CodeGen.X86 +{ + static class X86Optimizer + { + private const int MaxConstantUses = 10000; + + public static void RunPass(ControlFlowGraph cfg) + { + var constants = new Dictionary(); + + Operand GetConstantCopy(BasicBlock block, Operation operation, Operand source) + { + // If the constant has many uses, we also force a new constant mov to be added, in order + // to avoid overflow of the counts field (that is limited to 16 bits). + if (!constants.TryGetValue(source.Value, out var constant) || constant.UsesCount > MaxConstantUses) + { + constant = Local(source.Type); + + Operation copyOp = Operation(Instruction.Copy, constant, source); + + block.Operations.AddBefore(operation, copyOp); + + constants[source.Value] = constant; + } + + return constant; + } + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + constants.Clear(); + + Operation nextNode; + + for (Operation node = block.Operations.First; node != default; node = nextNode) + { + nextNode = node.ListNext; + + // Insert copies for constants that can't fit on a 32-bits immediate. + // Doing this early unblocks a few optimizations. + if (node.Instruction == Instruction.Add) + { + Operand src1 = node.GetSource(0); + Operand src2 = node.GetSource(1); + + if (src1.Kind == OperandKind.Constant && (src1.Relocatable || CodeGenCommon.IsLongConst(src1))) + { + node.SetSource(0, GetConstantCopy(block, node, src1)); + } + + if (src2.Kind == OperandKind.Constant && (src2.Relocatable || CodeGenCommon.IsLongConst(src2))) + { + node.SetSource(1, GetConstantCopy(block, node, src2)); + } + } + + // Try to fold something like: + // shl rbx, 2 + // add rax, rbx + // add rax, 0xcafe + // mov rax, [rax] + // Into: + // mov rax, [rax+rbx*4+0xcafe] + if (IsMemoryLoadOrStore(node.Instruction)) + { + OperandType type; + + if (node.Destination != default) + { + type = node.Destination.Type; + } + else + { + type = node.GetSource(1).Type; + } + + Operand memOp = GetMemoryOperandOrNull(node.GetSource(0), type); + + if (memOp != default) + { + node.SetSource(0, memOp); + } + } + } + } + + Optimizer.RemoveUnusedNodes(cfg); + } + + private static Operand GetMemoryOperandOrNull(Operand addr, OperandType type) + { + Operand baseOp = addr; + + // First we check if the address is the result of a local X with 32-bits immediate + // addition. If that is the case, then the baseOp is X, and the memory operand immediate + // becomes the addition immediate. Otherwise baseOp keeps being the address. + int imm = GetConstOp(ref baseOp); + + // Now we check if the baseOp is the result of a local Y with a local Z addition. + // If that is the case, we now set baseOp to Y and indexOp to Z. We further check + // if Z is the result of a left shift of local W by a value >= 0 and <= 3, if that + // is the case, we set indexOp to W and adjust the scale value of the memory operand + // to match that of the left shift. + // There is one missed case, which is the address being a shift result, but this is + // probably not worth optimizing as it should never happen. + (Operand indexOp, Multiplier scale) = GetIndexOp(ref baseOp); + + // If baseOp is still equal to address, then there's nothing that can be optimized. + if (baseOp == addr) + { + return default; + } + + if (imm == 0 && scale == Multiplier.x1 && indexOp != default) + { + imm = GetConstOp(ref indexOp); + } + + return MemoryOp(type, baseOp, indexOp, scale, imm); + } + + private static int GetConstOp(ref Operand baseOp) + { + Operation operation = GetAsgOpWithInst(baseOp, Instruction.Add); + + if (operation == default) + { + return 0; + } + + Operand src1 = operation.GetSource(0); + Operand src2 = operation.GetSource(1); + + Operand constOp; + Operand otherOp; + + if (src1.Kind == OperandKind.Constant && src2.Kind == OperandKind.LocalVariable) + { + constOp = src1; + otherOp = src2; + } + else if (src1.Kind == OperandKind.LocalVariable && src2.Kind == OperandKind.Constant) + { + constOp = src2; + otherOp = src1; + } + else + { + return 0; + } + + // If we have addition by 64-bits constant, then we can't optimize it further, + // as we can't encode a 64-bits immediate on the memory operand. + if (CodeGenCommon.IsLongConst(constOp)) + { + return 0; + } + + baseOp = otherOp; + + return constOp.AsInt32(); + } + + private static (Operand, Multiplier) GetIndexOp(ref Operand baseOp) + { + Operand indexOp = default; + + Multiplier scale = Multiplier.x1; + + Operation addOp = GetAsgOpWithInst(baseOp, Instruction.Add); + + if (addOp == default) + { + return (indexOp, scale); + } + + Operand src1 = addOp.GetSource(0); + Operand src2 = addOp.GetSource(1); + + if (src1.Kind != OperandKind.LocalVariable || src2.Kind != OperandKind.LocalVariable) + { + return (indexOp, scale); + } + + baseOp = src1; + indexOp = src2; + + Operation shlOp = GetAsgOpWithInst(src1, Instruction.ShiftLeft); + + bool indexOnSrc2 = false; + + if (shlOp == default) + { + shlOp = GetAsgOpWithInst(src2, Instruction.ShiftLeft); + + indexOnSrc2 = true; + } + + if (shlOp != default) + { + Operand shSrc = shlOp.GetSource(0); + Operand shift = shlOp.GetSource(1); + + if (shSrc.Kind == OperandKind.LocalVariable && shift.Kind == OperandKind.Constant && shift.Value <= 3) + { + scale = shift.Value switch + { + 1 => Multiplier.x2, + 2 => Multiplier.x4, + 3 => Multiplier.x8, + _ => Multiplier.x1 + }; + + baseOp = indexOnSrc2 ? src1 : src2; + indexOp = shSrc; + } + } + + return (indexOp, scale); + } + + private static Operation GetAsgOpWithInst(Operand op, Instruction inst) + { + // If we have multiple assignments, folding is not safe + // as the value may be different depending on the + // control flow path. + if (op.AssignmentsCount != 1) + { + return default; + } + + Operation asgOp = op.Assignments[0]; + + if (asgOp.Instruction != inst) + { + return default; + } + + return asgOp; + } + + private static bool IsMemoryLoadOrStore(Instruction inst) + { + return inst == Instruction.Load || + inst == Instruction.Load16 || + inst == Instruction.Load8 || + inst == Instruction.Store || + inst == Instruction.Store16 || + inst == Instruction.Store8; + } + } +} diff --git a/src/ARMeilleure/CodeGen/X86/X86Register.cs b/src/ARMeilleure/CodeGen/X86/X86Register.cs new file mode 100644 index 00000000..01f63e31 --- /dev/null +++ b/src/ARMeilleure/CodeGen/X86/X86Register.cs @@ -0,0 +1,41 @@ +namespace ARMeilleure.CodeGen.X86 +{ + enum X86Register + { + Invalid = -1, + + Rax = 0, + Rcx = 1, + Rdx = 2, + Rbx = 3, + Rsp = 4, + Rbp = 5, + Rsi = 6, + Rdi = 7, + R8 = 8, + R9 = 9, + R10 = 10, + R11 = 11, + R12 = 12, + R13 = 13, + R14 = 14, + R15 = 15, + + Xmm0 = 0, + Xmm1 = 1, + Xmm2 = 2, + Xmm3 = 3, + Xmm4 = 4, + Xmm5 = 5, + Xmm6 = 6, + Xmm7 = 7, + Xmm8 = 8, + Xmm9 = 9, + Xmm10 = 10, + Xmm11 = 11, + Xmm12 = 12, + Xmm13 = 13, + Xmm14 = 14, + Xmm15 = 15 + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Common/AddressTable.cs b/src/ARMeilleure/Common/AddressTable.cs new file mode 100644 index 00000000..9db2d00d --- /dev/null +++ b/src/ARMeilleure/Common/AddressTable.cs @@ -0,0 +1,252 @@ +using ARMeilleure.Diagnostics; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Common +{ + /// + /// Represents a table of guest address to a value. + /// + /// Type of the value + unsafe class AddressTable : IDisposable where TEntry : unmanaged + { + /// + /// Represents a level in an . + /// + public readonly struct Level + { + /// + /// Gets the index of the in the guest address. + /// + public int Index { get; } + + /// + /// Gets the length of the in the guest address. + /// + public int Length { get; } + + /// + /// Gets the mask which masks the bits used by the . + /// + public ulong Mask => ((1ul << Length) - 1) << Index; + + /// + /// Initializes a new instance of the structure with the specified + /// and . + /// + /// Index of the + /// Length of the + public Level(int index, int length) + { + (Index, Length) = (index, length); + } + + /// + /// Gets the value of the from the specified guest . + /// + /// Guest address + /// Value of the from the specified guest + public int GetValue(ulong address) + { + return (int)((address & Mask) >> Index); + } + } + + private bool _disposed; + private TEntry** _table; + private readonly List _pages; + + /// + /// Gets the bits used by the of the instance. + /// + public ulong Mask { get; } + + /// + /// Gets the s used by the instance. + /// + public Level[] Levels { get; } + + /// + /// Gets or sets the default fill value of newly created leaf pages. + /// + public TEntry Fill { get; set; } + + /// + /// Gets the base address of the . + /// + /// instance was disposed + public IntPtr Base + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_pages) + { + return (IntPtr)GetRootPage(); + } + } + } + + /// + /// Constructs a new instance of the class with the specified list of + /// . + /// + /// is null + /// Length of is less than 2 + public AddressTable(Level[] levels) + { + ArgumentNullException.ThrowIfNull(levels); + + if (levels.Length < 2) + { + throw new ArgumentException("Table must be at least 2 levels deep.", nameof(levels)); + } + + _pages = new List(capacity: 16); + + Levels = levels; + Mask = 0; + + foreach (var level in Levels) + { + Mask |= level.Mask; + } + } + + /// + /// Determines if the specified is in the range of the + /// . + /// + /// Guest address + /// if is valid; otherwise + public bool IsValid(ulong address) + { + return (address & ~Mask) == 0; + } + + /// + /// Gets a reference to the value at the specified guest . + /// + /// Guest address + /// Reference to the value at the specified guest + /// instance was disposed + /// is not mapped + public ref TEntry GetValue(ulong address) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + if (!IsValid(address)) + { + throw new ArgumentException($"Address 0x{address:X} is not mapped onto the table.", nameof(address)); + } + + lock (_pages) + { + return ref GetPage(address)[Levels[^1].GetValue(address)]; + } + } + + /// + /// Gets the leaf page for the specified guest . + /// + /// Guest address + /// Leaf page for the specified guest + private TEntry* GetPage(ulong address) + { + TEntry** page = GetRootPage(); + + for (int i = 0; i < Levels.Length - 1; i++) + { + ref Level level = ref Levels[i]; + ref TEntry* nextPage = ref page[level.GetValue(address)]; + + if (nextPage == null) + { + ref Level nextLevel = ref Levels[i + 1]; + + nextPage = i == Levels.Length - 2 ? + (TEntry*)Allocate(1 << nextLevel.Length, Fill, leaf: true) : + (TEntry*)Allocate(1 << nextLevel.Length, IntPtr.Zero, leaf: false); + } + + page = (TEntry**)nextPage; + } + + return (TEntry*)page; + } + + /// + /// Lazily initialize and get the root page of the . + /// + /// Root page of the + private TEntry** GetRootPage() + { + if (_table == null) + { + _table = (TEntry**)Allocate(1 << Levels[0].Length, fill: IntPtr.Zero, leaf: false); + } + + return _table; + } + + /// + /// Allocates a block of memory of the specified type and length. + /// + /// Type of elements + /// Number of elements + /// Fill value + /// if leaf; otherwise + /// Allocated block + private IntPtr Allocate(int length, T fill, bool leaf) where T : unmanaged + { + var size = sizeof(T) * length; + var page = (IntPtr)NativeAllocator.Instance.Allocate((uint)size); + var span = new Span((void*)page, length); + + span.Fill(fill); + + _pages.Add(page); + + TranslatorEventSource.Log.AddressTableAllocated(size, leaf); + + return page; + } + + /// + /// Releases all resources used by the instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases all unmanaged and optionally managed resources used by the + /// instance. + /// + /// to dispose managed resources also; otherwise just unmanaged resouces + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + foreach (var page in _pages) + { + Marshal.FreeHGlobal(page); + } + + _disposed = true; + } + } + + /// + /// Frees resources used by the instance. + /// + ~AddressTable() + { + Dispose(false); + } + } +} diff --git a/src/ARMeilleure/Common/Allocator.cs b/src/ARMeilleure/Common/Allocator.cs new file mode 100644 index 00000000..247a8e8b --- /dev/null +++ b/src/ARMeilleure/Common/Allocator.cs @@ -0,0 +1,24 @@ +using System; + +namespace ARMeilleure.Common +{ + unsafe abstract class Allocator : IDisposable + { + public T* Allocate(ulong count = 1) where T : unmanaged + { + return (T*)Allocate(count * (uint)sizeof(T)); + } + + public abstract void* Allocate(ulong size); + + public abstract void Free(void* block); + + protected virtual void Dispose(bool disposing) { } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/ARMeilleure/Common/ArenaAllocator.cs b/src/ARMeilleure/Common/ArenaAllocator.cs new file mode 100644 index 00000000..bce6794a --- /dev/null +++ b/src/ARMeilleure/Common/ArenaAllocator.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.Common +{ + unsafe sealed class ArenaAllocator : Allocator + { + private class PageInfo + { + public byte* Pointer; + public byte Unused; + public int UnusedCounter; + } + + private int _lastReset; + private ulong _index; + private int _pageIndex; + private PageInfo _page; + private List _pages; + private readonly ulong _pageSize; + private readonly uint _pageCount; + private readonly List _extras; + + public ArenaAllocator(uint pageSize, uint pageCount) + { + _lastReset = Environment.TickCount; + + // Set _index to pageSize so that the first allocation goes through the slow path. + _index = pageSize; + _pageIndex = -1; + + _page = null; + _pages = new List(); + _pageSize = pageSize; + _pageCount = pageCount; + + _extras = new List(); + } + + public Span AllocateSpan(ulong count) where T : unmanaged + { + return new Span(Allocate(count), (int)count); + } + + public override void* Allocate(ulong size) + { + if (_index + size <= _pageSize) + { + byte* result = _page.Pointer + _index; + + _index += size; + + return result; + } + + return AllocateSlow(size); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void* AllocateSlow(ulong size) + { + if (size > _pageSize) + { + void* extra = NativeAllocator.Instance.Allocate(size); + + _extras.Add((IntPtr)extra); + + return extra; + } + + if (_index + size > _pageSize) + { + _index = 0; + _pageIndex++; + } + + if (_pageIndex < _pages.Count) + { + _page = _pages[_pageIndex]; + _page.Unused = 0; + } + else + { + _page = new PageInfo(); + _page.Pointer = (byte*)NativeAllocator.Instance.Allocate(_pageSize); + + _pages.Add(_page); + } + + byte* result = _page.Pointer + _index; + + _index += size; + + return result; + } + + public override void Free(void* block) { } + + public void Reset() + { + _index = _pageSize; + _pageIndex = -1; + _page = null; + + // Free excess pages that was allocated. + while (_pages.Count > _pageCount) + { + NativeAllocator.Instance.Free(_pages[_pages.Count - 1].Pointer); + + _pages.RemoveAt(_pages.Count - 1); + } + + // Free extra blocks that are not page-sized + foreach (IntPtr ptr in _extras) + { + NativeAllocator.Instance.Free((void*)ptr); + } + + _extras.Clear(); + + // Free pooled pages that has not been used in a while. Remove pages at the back first, because we try to + // keep the pages at the front alive, since they're more likely to be hot and in the d-cache. + bool removing = true; + + // If arena is used frequently, keep pages for longer. Otherwise keep pages for a shorter amount of time. + int now = Environment.TickCount; + int count = (now - _lastReset) switch { + >= 5000 => 0, + >= 2500 => 50, + >= 1000 => 100, + >= 10 => 1500, + _ => 5000 + }; + + for (int i = _pages.Count - 1; i >= 0; i--) + { + PageInfo page = _pages[i]; + + if (page.Unused == 0) + { + page.UnusedCounter = 0; + } + + page.UnusedCounter += page.Unused; + page.Unused = 1; + + // If page not used after `count` resets, remove it. + if (removing && page.UnusedCounter >= count) + { + NativeAllocator.Instance.Free(page.Pointer); + + _pages.RemoveAt(i); + } + else + { + removing = false; + } + } + + _lastReset = now; + } + + protected override void Dispose(bool disposing) + { + if (_pages != null) + { + foreach (PageInfo info in _pages) + { + NativeAllocator.Instance.Free(info.Pointer); + } + + foreach (IntPtr ptr in _extras) + { + NativeAllocator.Instance.Free((void*)ptr); + } + + _pages = null; + } + } + + ~ArenaAllocator() + { + Dispose(false); + } + } +} diff --git a/src/ARMeilleure/Common/BitMap.cs b/src/ARMeilleure/Common/BitMap.cs new file mode 100644 index 00000000..27ef031f --- /dev/null +++ b/src/ARMeilleure/Common/BitMap.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.Common +{ + unsafe class BitMap : IEnumerable, IDisposable + { + private const int IntSize = 64; + private const int IntMask = IntSize - 1; + + private int _count; + private long* _masks; + private readonly Allocator _allocator; + + public BitMap(Allocator allocator) + { + _allocator = allocator; + } + + public BitMap(Allocator allocator, int capacity) : this(allocator) + { + EnsureCapacity(capacity); + } + + public bool Set(int bit) + { + EnsureCapacity(bit + 1); + + int wordIndex = bit / IntSize; + int wordBit = bit & IntMask; + + long wordMask = 1L << wordBit; + + if ((_masks[wordIndex] & wordMask) != 0) + { + return false; + } + + _masks[wordIndex] |= wordMask; + + return true; + } + + public void Clear(int bit) + { + EnsureCapacity(bit + 1); + + int wordIndex = bit / IntSize; + int wordBit = bit & IntMask; + + long wordMask = 1L << wordBit; + + _masks[wordIndex] &= ~wordMask; + } + + public bool IsSet(int bit) + { + EnsureCapacity(bit + 1); + + int wordIndex = bit / IntSize; + int wordBit = bit & IntMask; + + return (_masks[wordIndex] & (1L << wordBit)) != 0; + } + + public int FindFirstUnset() + { + for (int index = 0; index < _count; index++) + { + long mask = _masks[index]; + + if (mask != -1L) + { + return BitOperations.TrailingZeroCount(~mask) + index * IntSize; + } + } + + return _count * IntSize; + } + + public bool Set(BitMap map) + { + EnsureCapacity(map._count * IntSize); + + bool modified = false; + + for (int index = 0; index < _count; index++) + { + long newValue = _masks[index] | map._masks[index]; + + if (_masks[index] != newValue) + { + _masks[index] = newValue; + + modified = true; + } + } + + return modified; + } + + public bool Clear(BitMap map) + { + EnsureCapacity(map._count * IntSize); + + bool modified = false; + + for (int index = 0; index < _count; index++) + { + long newValue = _masks[index] & ~map._masks[index]; + + if (_masks[index] != newValue) + { + _masks[index] = newValue; + + modified = true; + } + } + + return modified; + } + + private void EnsureCapacity(int size) + { + int count = (size + IntMask) / IntSize; + + if (count > _count) + { + var oldMask = _masks; + var oldSpan = new Span(_masks, _count); + + _masks = _allocator.Allocate((uint)count); + _count = count; + + var newSpan = new Span(_masks, _count); + + oldSpan.CopyTo(newSpan); + newSpan.Slice(oldSpan.Length).Clear(); + + _allocator.Free(oldMask); + } + } + + public void Dispose() + { + if (_masks != null) + { + _allocator.Free(_masks); + + _masks = null; + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + public struct Enumerator : IEnumerator + { + private long _index; + private long _mask; + private int _bit; + private readonly BitMap _map; + + public int Current => (int)_index * IntSize + _bit; + object IEnumerator.Current => Current; + + public Enumerator(BitMap map) + { + _index = -1; + _mask = 0; + _bit = 0; + _map = map; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + if (_mask != 0) + { + _mask &= ~(1L << _bit); + } + + // Manually hoist these loads, because RyuJIT does not. + long count = (uint)_map._count; + long* masks = _map._masks; + + while (_mask == 0) + { + if (++_index >= count) + { + return false; + } + + _mask = masks[_index]; + } + + _bit = BitOperations.TrailingZeroCount(_mask); + + return true; + } + + public void Reset() { } + + public void Dispose() { } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Common/BitUtils.cs b/src/ARMeilleure/Common/BitUtils.cs new file mode 100644 index 00000000..e7697ff3 --- /dev/null +++ b/src/ARMeilleure/Common/BitUtils.cs @@ -0,0 +1,57 @@ +using System; +using System.Numerics; + +namespace ARMeilleure.Common +{ + static class BitUtils + { + private static ReadOnlySpan HbsNibbleLut => new sbyte[] { -1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3 }; + + public static long FillWithOnes(int bits) + { + return bits == 64 ? -1L : (1L << bits) - 1; + } + + public static int HighestBitSet(int value) + { + return 31 - BitOperations.LeadingZeroCount((uint)value); + } + + public static int HighestBitSetNibble(int value) + { + return HbsNibbleLut[value]; + } + + public static long Replicate(long bits, int size) + { + long output = 0; + + for (int bit = 0; bit < 64; bit += size) + { + output |= bits << bit; + } + + return output; + } + + public static int RotateRight(int bits, int shift, int size) + { + return (int)RotateRight((uint)bits, shift, size); + } + + public static uint RotateRight(uint bits, int shift, int size) + { + return (bits >> shift) | (bits << (size - shift)); + } + + public static long RotateRight(long bits, int shift, int size) + { + return (long)RotateRight((ulong)bits, shift, size); + } + + public static ulong RotateRight(ulong bits, int shift, int size) + { + return (bits >> shift) | (bits << (size - shift)); + } + } +} diff --git a/src/ARMeilleure/Common/Counter.cs b/src/ARMeilleure/Common/Counter.cs new file mode 100644 index 00000000..d7210d15 --- /dev/null +++ b/src/ARMeilleure/Common/Counter.cs @@ -0,0 +1,98 @@ +using System; + +namespace ARMeilleure.Common +{ + /// + /// Represents a numeric counter which can be used for instrumentation of compiled code. + /// + /// Type of the counter + class Counter : IDisposable where T : unmanaged + { + private bool _disposed; + /// + /// Index in the + /// + private readonly int _index; + private readonly EntryTable _countTable; + + /// + /// Initializes a new instance of the class from the specified + /// instance and index. + /// + /// instance + /// is + /// is unsupported + public Counter(EntryTable countTable) + { + if (typeof(T) != typeof(byte) && typeof(T) != typeof(sbyte) && + typeof(T) != typeof(short) && typeof(T) != typeof(ushort) && + typeof(T) != typeof(int) && typeof(T) != typeof(uint) && + typeof(T) != typeof(long) && typeof(T) != typeof(ulong) && + typeof(T) != typeof(nint) && typeof(T) != typeof(nuint) && + typeof(T) != typeof(float) && typeof(T) != typeof(double)) + { + throw new ArgumentException("Counter does not support the specified type."); + } + + _countTable = countTable ?? throw new ArgumentNullException(nameof(countTable)); + _index = countTable.Allocate(); + } + + /// + /// Gets a reference to the value of the counter. + /// + /// instance was disposed + /// + /// This can refer to freed memory if the owning is disposed. + /// + public ref T Value + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + return ref _countTable.GetValue(_index); + } + } + + /// + /// Releases all resources used by the instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases all unmanaged and optionally managed resources used by the instance. + /// + /// to dispose managed resources also; otherwise just unmanaged resources + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + try + { + // The index into the EntryTable is essentially an unmanaged resource since we allocate and free the + // resource ourselves. + _countTable.Free(_index); + } + catch (ObjectDisposedException) + { + // Can happen because _countTable may be disposed before the Counter instance. + } + + _disposed = true; + } + } + + /// + /// Frees resources used by the instance. + /// + ~Counter() + { + Dispose(false); + } + } +} diff --git a/src/ARMeilleure/Common/EntryTable.cs b/src/ARMeilleure/Common/EntryTable.cs new file mode 100644 index 00000000..6f205797 --- /dev/null +++ b/src/ARMeilleure/Common/EntryTable.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Numerics; + +namespace ARMeilleure.Common +{ + /// + /// Represents an expandable table of the type , whose entries will remain at the same + /// address through out the table's lifetime. + /// + /// Type of the entry in the table + class EntryTable : IDisposable where TEntry : unmanaged + { + private bool _disposed; + private int _freeHint; + private readonly int _pageCapacity; // Number of entries per page. + private readonly int _pageLogCapacity; + private readonly Dictionary _pages; + private readonly BitMap _allocated; + + /// + /// Initializes a new instance of the class with the desired page size in + /// bytes. + /// + /// Desired page size in bytes + /// is less than 0 + /// 's size is zero + /// + /// The actual page size may be smaller or larger depending on the size of . + /// + public unsafe EntryTable(int pageSize = 4096) + { + if (pageSize < 0) + { + throw new ArgumentOutOfRangeException(nameof(pageSize), "Page size cannot be negative."); + } + + if (sizeof(TEntry) == 0) + { + throw new ArgumentException("Size of TEntry cannot be zero."); + } + + _allocated = new BitMap(NativeAllocator.Instance); + _pages = new Dictionary(); + _pageLogCapacity = BitOperations.Log2((uint)(pageSize / sizeof(TEntry))); + _pageCapacity = 1 << _pageLogCapacity; + } + + /// + /// Allocates an entry in the . + /// + /// Index of entry allocated in the table + /// instance was disposed + public int Allocate() + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_allocated) + { + if (_allocated.IsSet(_freeHint)) + { + _freeHint = _allocated.FindFirstUnset(); + } + + int index = _freeHint++; + var page = GetPage(index); + + _allocated.Set(index); + + GetValue(page, index) = default; + + return index; + } + } + + /// + /// Frees the entry at the specified . + /// + /// Index of entry to free + /// instance was disposed + public void Free(int index) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_allocated) + { + if (_allocated.IsSet(index)) + { + _allocated.Clear(index); + + _freeHint = index; + } + } + } + + /// + /// Gets a reference to the entry at the specified allocated . + /// + /// Index of the entry + /// Reference to the entry at the specified + /// instance was disposed + /// Entry at is not allocated + public ref TEntry GetValue(int index) + { + ObjectDisposedException.ThrowIf(_disposed, this); + + lock (_allocated) + { + if (!_allocated.IsSet(index)) + { + throw new ArgumentException("Entry at the specified index was not allocated", nameof(index)); + } + + var page = GetPage(index); + + return ref GetValue(page, index); + } + } + + /// + /// Gets a reference to the entry at using the specified from the specified + /// . + /// + /// Page to use + /// Index to use + /// Reference to the entry + private ref TEntry GetValue(Span page, int index) + { + return ref page[index & (_pageCapacity - 1)]; + } + + /// + /// Gets the page for the specified . + /// + /// Index to use + /// Page for the specified + private unsafe Span GetPage(int index) + { + var pageIndex = (int)((uint)(index & ~(_pageCapacity - 1)) >> _pageLogCapacity); + + if (!_pages.TryGetValue(pageIndex, out IntPtr page)) + { + page = (IntPtr)NativeAllocator.Instance.Allocate((uint)sizeof(TEntry) * (uint)_pageCapacity); + + _pages.Add(pageIndex, page); + } + + return new Span((void*)page, _pageCapacity); + } + + /// + /// Releases all resources used by the instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases all unmanaged and optionally managed resources used by the + /// instance. + /// + /// to dispose managed resources also; otherwise just unmanaged resouces + protected unsafe virtual void Dispose(bool disposing) + { + if (!_disposed) + { + _allocated.Dispose(); + + foreach (var page in _pages.Values) + { + NativeAllocator.Instance.Free((void*)page); + } + + _disposed = true; + } + } + + /// + /// Frees resources used by the instance. + /// + ~EntryTable() + { + Dispose(false); + } + } +} diff --git a/src/ARMeilleure/Common/EnumUtils.cs b/src/ARMeilleure/Common/EnumUtils.cs new file mode 100644 index 00000000..2a4aa645 --- /dev/null +++ b/src/ARMeilleure/Common/EnumUtils.cs @@ -0,0 +1,12 @@ +using System; + +namespace ARMeilleure.Common +{ + static class EnumUtils + { + public static int GetCount(Type enumType) + { + return Enum.GetNames(enumType).Length; + } + } +} diff --git a/src/ARMeilleure/Common/NativeAllocator.cs b/src/ARMeilleure/Common/NativeAllocator.cs new file mode 100644 index 00000000..71c04a9b --- /dev/null +++ b/src/ARMeilleure/Common/NativeAllocator.cs @@ -0,0 +1,27 @@ +using System; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Common +{ + unsafe sealed class NativeAllocator : Allocator + { + public static NativeAllocator Instance { get; } = new(); + + public override void* Allocate(ulong size) + { + void* result = (void*)Marshal.AllocHGlobal((IntPtr)size); + + if (result == null) + { + throw new OutOfMemoryException(); + } + + return result; + } + + public override void Free(void* block) + { + Marshal.FreeHGlobal((IntPtr)block); + } + } +} diff --git a/src/ARMeilleure/Decoders/Block.cs b/src/ARMeilleure/Decoders/Block.cs new file mode 100644 index 00000000..f296d299 --- /dev/null +++ b/src/ARMeilleure/Decoders/Block.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; + +namespace ARMeilleure.Decoders +{ + class Block + { + public ulong Address { get; set; } + public ulong EndAddress { get; set; } + + public Block Next { get; set; } + public Block Branch { get; set; } + + public bool Exit { get; set; } + + public List OpCodes { get; } + + public Block() + { + OpCodes = new List(); + } + + public Block(ulong address) : this() + { + Address = address; + } + + public void Split(Block rightBlock) + { + int splitIndex = BinarySearch(OpCodes, rightBlock.Address); + + if (OpCodes[splitIndex].Address < rightBlock.Address) + { + splitIndex++; + } + + int splitCount = OpCodes.Count - splitIndex; + + if (splitCount <= 0) + { + throw new ArgumentException("Can't split at right block address."); + } + + rightBlock.EndAddress = EndAddress; + + rightBlock.Next = Next; + rightBlock.Branch = Branch; + + rightBlock.OpCodes.AddRange(OpCodes.GetRange(splitIndex, splitCount)); + + EndAddress = rightBlock.Address; + + Next = rightBlock; + Branch = null; + + OpCodes.RemoveRange(splitIndex, splitCount); + } + + private static int BinarySearch(List opCodes, ulong address) + { + int left = 0; + int middle = 0; + int right = opCodes.Count - 1; + + while (left <= right) + { + int size = right - left; + + middle = left + (size >> 1); + + OpCode opCode = opCodes[middle]; + + if (address == (ulong)opCode.Address) + { + break; + } + + if (address < (ulong)opCode.Address) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return middle; + } + + public OpCode GetLastOp() + { + if (OpCodes.Count > 0) + { + return OpCodes[OpCodes.Count - 1]; + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/Condition.cs b/src/ARMeilleure/Decoders/Condition.cs new file mode 100644 index 00000000..727f897d --- /dev/null +++ b/src/ARMeilleure/Decoders/Condition.cs @@ -0,0 +1,32 @@ +namespace ARMeilleure.Decoders +{ + enum Condition + { + Eq = 0, + Ne = 1, + GeUn = 2, + LtUn = 3, + Mi = 4, + Pl = 5, + Vs = 6, + Vc = 7, + GtUn = 8, + LeUn = 9, + Ge = 10, + Lt = 11, + Gt = 12, + Le = 13, + Al = 14, + Nv = 15 + } + + static class ConditionExtensions + { + public static Condition Invert(this Condition cond) + { + // Bit 0 of all conditions is basically a negation bit, so + // inverting this bit has the effect of inverting the condition. + return (Condition)((int)cond ^ 1); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/DataOp.cs b/src/ARMeilleure/Decoders/DataOp.cs new file mode 100644 index 00000000..464d0089 --- /dev/null +++ b/src/ARMeilleure/Decoders/DataOp.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + enum DataOp + { + Adr = 0, + Arithmetic = 1, + Logical = 2, + BitField = 3 + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/Decoder.cs b/src/ARMeilleure/Decoders/Decoder.cs new file mode 100644 index 00000000..426465aa --- /dev/null +++ b/src/ARMeilleure/Decoders/Decoder.cs @@ -0,0 +1,391 @@ +using ARMeilleure.Decoders.Optimizations; +using ARMeilleure.Instructions; +using ARMeilleure.Memory; +using ARMeilleure.State; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace ARMeilleure.Decoders +{ + static class Decoder + { + // We define a limit on the number of instructions that a function may have, + // this prevents functions being potentially too large, which would + // take too long to compile and use too much memory. + private const int MaxInstsPerFunction = 2500; + + // For lower code quality translation, we set a lower limit since we're blocking execution. + private const int MaxInstsPerFunctionLowCq = 500; + + public static Block[] Decode(IMemoryManager memory, ulong address, ExecutionMode mode, bool highCq, DecoderMode dMode) + { + List blocks = new List(); + + Queue workQueue = new Queue(); + + Dictionary visited = new Dictionary(); + + Debug.Assert(MaxInstsPerFunctionLowCq <= MaxInstsPerFunction); + + int opsCount = 0; + + int instructionLimit = highCq ? MaxInstsPerFunction : MaxInstsPerFunctionLowCq; + + Block GetBlock(ulong blkAddress) + { + if (!visited.TryGetValue(blkAddress, out Block block)) + { + block = new Block(blkAddress); + + if ((dMode != DecoderMode.MultipleBlocks && visited.Count >= 1) || opsCount > instructionLimit || !memory.IsMapped(blkAddress)) + { + block.Exit = true; + block.EndAddress = blkAddress; + } + + workQueue.Enqueue(block); + + visited.Add(blkAddress, block); + } + + return block; + } + + GetBlock(address); + + while (workQueue.TryDequeue(out Block currBlock)) + { + // Check if the current block is inside another block. + if (BinarySearch(blocks, currBlock.Address, out int nBlkIndex)) + { + Block nBlock = blocks[nBlkIndex]; + + if (nBlock.Address == currBlock.Address) + { + throw new InvalidOperationException("Found duplicate block address on the list."); + } + + currBlock.Exit = false; + + nBlock.Split(currBlock); + + blocks.Insert(nBlkIndex + 1, currBlock); + + continue; + } + + if (!currBlock.Exit) + { + // If we have a block after the current one, set the limit address. + ulong limitAddress = ulong.MaxValue; + + if (nBlkIndex != blocks.Count) + { + Block nBlock = blocks[nBlkIndex]; + + int nextIndex = nBlkIndex + 1; + + if (nBlock.Address < currBlock.Address && nextIndex < blocks.Count) + { + limitAddress = blocks[nextIndex].Address; + } + else if (nBlock.Address > currBlock.Address) + { + limitAddress = blocks[nBlkIndex].Address; + } + } + + if (dMode == DecoderMode.SingleInstruction) + { + // Only read at most one instruction + limitAddress = currBlock.Address + 1; + } + + FillBlock(memory, mode, currBlock, limitAddress); + + opsCount += currBlock.OpCodes.Count; + + if (currBlock.OpCodes.Count != 0) + { + // Set child blocks. "Branch" is the block the branch instruction + // points to (when taken), "Next" is the block at the next address, + // executed when the branch is not taken. For Unconditional Branches + // (except BL/BLR that are sub calls) or end of executable, Next is null. + OpCode lastOp = currBlock.GetLastOp(); + + bool isCall = IsCall(lastOp); + + if (lastOp is IOpCodeBImm op && !isCall) + { + currBlock.Branch = GetBlock((ulong)op.Immediate); + } + + if (isCall || !(IsUnconditionalBranch(lastOp) || IsTrap(lastOp))) + { + currBlock.Next = GetBlock(currBlock.EndAddress); + } + } + } + + // Insert the new block on the list (sorted by address). + if (blocks.Count != 0) + { + Block nBlock = blocks[nBlkIndex]; + + blocks.Insert(nBlkIndex + (nBlock.Address < currBlock.Address ? 1 : 0), currBlock); + } + else + { + blocks.Add(currBlock); + } + } + + if (blocks.Count == 1 && blocks[0].OpCodes.Count == 0) + { + Debug.Assert(blocks[0].Exit); + Debug.Assert(blocks[0].Address == blocks[0].EndAddress); + + throw new InvalidOperationException($"Decoded a single empty exit block. Entry point = 0x{address:X}."); + } + + if (dMode == DecoderMode.MultipleBlocks) + { + return TailCallRemover.RunPass(address, blocks); + } + else + { + return blocks.ToArray(); + } + } + + public static bool BinarySearch(List blocks, ulong address, out int index) + { + index = 0; + + int left = 0; + int right = blocks.Count - 1; + + while (left <= right) + { + int size = right - left; + + int middle = left + (size >> 1); + + Block block = blocks[middle]; + + index = middle; + + if (address >= block.Address && address < block.EndAddress) + { + return true; + } + + if (address < block.Address) + { + right = middle - 1; + } + else + { + left = middle + 1; + } + } + + return false; + } + + private static void FillBlock( + IMemoryManager memory, + ExecutionMode mode, + Block block, + ulong limitAddress) + { + ulong address = block.Address; + int itBlockSize = 0; + + OpCode opCode; + + do + { + if (address >= limitAddress && itBlockSize == 0) + { + break; + } + + opCode = DecodeOpCode(memory, address, mode); + + block.OpCodes.Add(opCode); + + address += (ulong)opCode.OpCodeSizeInBytes; + + if (opCode is OpCodeT16IfThen it) + { + itBlockSize = it.IfThenBlockSize; + } + else if (itBlockSize > 0) + { + itBlockSize--; + } + } + while (!(IsBranch(opCode) || IsException(opCode))); + + block.EndAddress = address; + } + + private static bool IsBranch(OpCode opCode) + { + return opCode is OpCodeBImm || + opCode is OpCodeBReg || IsAarch32Branch(opCode); + } + + private static bool IsUnconditionalBranch(OpCode opCode) + { + return opCode is OpCodeBImmAl || + opCode is OpCodeBReg || IsAarch32UnconditionalBranch(opCode); + } + + private static bool IsAarch32UnconditionalBranch(OpCode opCode) + { + if (!(opCode is OpCode32 op)) + { + return false; + } + + // Compare and branch instructions are always conditional. + if (opCode.Instruction.Name == InstName.Cbz || + opCode.Instruction.Name == InstName.Cbnz) + { + return false; + } + + // Note: On ARM32, most instructions have conditional execution, + // so there's no "Always" (unconditional) branch like on ARM64. + // We need to check if the condition is "Always" instead. + return IsAarch32Branch(op) && op.Cond >= Condition.Al; + } + + private static bool IsAarch32Branch(OpCode opCode) + { + // Note: On ARM32, most ALU operations can write to R15 (PC), + // so we must consider such operations as a branch in potential aswell. + if (opCode is IOpCode32Alu opAlu && opAlu.Rd == RegisterAlias.Aarch32Pc) + { + if (opCode is OpCodeT32) + { + return opCode.Instruction.Name != InstName.Tst && opCode.Instruction.Name != InstName.Teq && + opCode.Instruction.Name != InstName.Cmp && opCode.Instruction.Name != InstName.Cmn; + } + return true; + } + + // Same thing for memory operations. We have the cases where PC is a target + // register (Rt == 15 or (mask & (1 << 15)) != 0), and cases where there is + // a write back to PC (wback == true && Rn == 15), however the later may + // be "undefined" depending on the CPU, so compilers should not produce that. + if (opCode is IOpCode32Mem || opCode is IOpCode32MemMult) + { + int rt, rn; + + bool wBack, isLoad; + + if (opCode is IOpCode32Mem opMem) + { + rt = opMem.Rt; + rn = opMem.Rn; + wBack = opMem.WBack; + isLoad = opMem.IsLoad; + + // For the dual load, we also need to take into account the + // case were Rt2 == 15 (PC). + if (rt == 14 && opMem.Instruction.Name == InstName.Ldrd) + { + rt = RegisterAlias.Aarch32Pc; + } + } + else if (opCode is IOpCode32MemMult opMemMult) + { + const int pcMask = 1 << RegisterAlias.Aarch32Pc; + + rt = (opMemMult.RegisterMask & pcMask) != 0 ? RegisterAlias.Aarch32Pc : 0; + rn = opMemMult.Rn; + wBack = opMemMult.PostOffset != 0; + isLoad = opMemMult.IsLoad; + } + else + { + throw new NotImplementedException($"The type \"{opCode.GetType().Name}\" is not implemented on the decoder."); + } + + if ((rt == RegisterAlias.Aarch32Pc && isLoad) || + (rn == RegisterAlias.Aarch32Pc && wBack)) + { + return true; + } + } + + // Explicit branch instructions. + return opCode is IOpCode32BImm || + opCode is IOpCode32BReg; + } + + private static bool IsCall(OpCode opCode) + { + return opCode.Instruction.Name == InstName.Bl || + opCode.Instruction.Name == InstName.Blr || + opCode.Instruction.Name == InstName.Blx; + } + + private static bool IsException(OpCode opCode) + { + return IsTrap(opCode) || opCode.Instruction.Name == InstName.Svc; + } + + private static bool IsTrap(OpCode opCode) + { + return opCode.Instruction.Name == InstName.Brk || + opCode.Instruction.Name == InstName.Trap || + opCode.Instruction.Name == InstName.Und; + } + + public static OpCode DecodeOpCode(IMemoryManager memory, ulong address, ExecutionMode mode) + { + int opCode = memory.Read(address); + + InstDescriptor inst; + + OpCodeTable.MakeOp makeOp; + + if (mode == ExecutionMode.Aarch64) + { + (inst, makeOp) = OpCodeTable.GetInstA64(opCode); + } + else + { + if (mode == ExecutionMode.Aarch32Arm) + { + (inst, makeOp) = OpCodeTable.GetInstA32(opCode); + } + else /* if (mode == ExecutionMode.Aarch32Thumb) */ + { + (inst, makeOp) = OpCodeTable.GetInstT32(opCode); + } + } + + if (makeOp != null) + { + return makeOp(inst, address, opCode); + } + else + { + if (mode == ExecutionMode.Aarch32Thumb) + { + return new OpCodeT16(inst, address, opCode); + } + else + { + return new OpCode(inst, address, opCode); + } + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/DecoderHelper.cs b/src/ARMeilleure/Decoders/DecoderHelper.cs new file mode 100644 index 00000000..5227e6a1 --- /dev/null +++ b/src/ARMeilleure/Decoders/DecoderHelper.cs @@ -0,0 +1,167 @@ +using ARMeilleure.Common; + +namespace ARMeilleure.Decoders +{ + static class DecoderHelper + { + static DecoderHelper() + { + Imm8ToFP32Table = BuildImm8ToFP32Table(); + Imm8ToFP64Table = BuildImm8ToFP64Table(); + } + + public static readonly uint[] Imm8ToFP32Table; + public static readonly ulong[] Imm8ToFP64Table; + + private static uint[] BuildImm8ToFP32Table() + { + uint[] tbl = new uint[256]; + + for (int idx = 0; idx < tbl.Length; idx++) + { + tbl[idx] = ExpandImm8ToFP32((uint)idx); + } + + return tbl; + } + + private static ulong[] BuildImm8ToFP64Table() + { + ulong[] tbl = new ulong[256]; + + for (int idx = 0; idx < tbl.Length; idx++) + { + tbl[idx] = ExpandImm8ToFP64((ulong)idx); + } + + return tbl; + } + + // abcdefgh -> aBbbbbbc defgh000 00000000 00000000 (B = ~b) + private static uint ExpandImm8ToFP32(uint imm) + { + uint MoveBit(uint bits, int from, int to) + { + return ((bits >> from) & 1U) << to; + } + + return MoveBit(imm, 7, 31) | MoveBit(~imm, 6, 30) | + MoveBit(imm, 6, 29) | MoveBit( imm, 6, 28) | + MoveBit(imm, 6, 27) | MoveBit( imm, 6, 26) | + MoveBit(imm, 6, 25) | MoveBit( imm, 5, 24) | + MoveBit(imm, 4, 23) | MoveBit( imm, 3, 22) | + MoveBit(imm, 2, 21) | MoveBit( imm, 1, 20) | + MoveBit(imm, 0, 19); + } + + // abcdefgh -> aBbbbbbb bbcdefgh 00000000 00000000 00000000 00000000 00000000 00000000 (B = ~b) + private static ulong ExpandImm8ToFP64(ulong imm) + { + ulong MoveBit(ulong bits, int from, int to) + { + return ((bits >> from) & 1UL) << to; + } + + return MoveBit(imm, 7, 63) | MoveBit(~imm, 6, 62) | + MoveBit(imm, 6, 61) | MoveBit( imm, 6, 60) | + MoveBit(imm, 6, 59) | MoveBit( imm, 6, 58) | + MoveBit(imm, 6, 57) | MoveBit( imm, 6, 56) | + MoveBit(imm, 6, 55) | MoveBit( imm, 6, 54) | + MoveBit(imm, 5, 53) | MoveBit( imm, 4, 52) | + MoveBit(imm, 3, 51) | MoveBit( imm, 2, 50) | + MoveBit(imm, 1, 49) | MoveBit( imm, 0, 48); + } + + public struct BitMask + { + public long WMask; + public long TMask; + public int Pos; + public int Shift; + public bool IsUndefined; + + public static BitMask Invalid => new BitMask { IsUndefined = true }; + } + + public static BitMask DecodeBitMask(int opCode, bool immediate) + { + int immS = (opCode >> 10) & 0x3f; + int immR = (opCode >> 16) & 0x3f; + + int n = (opCode >> 22) & 1; + int sf = (opCode >> 31) & 1; + + int length = BitUtils.HighestBitSet((~immS & 0x3f) | (n << 6)); + + if (length < 1 || (sf == 0 && n != 0)) + { + return BitMask.Invalid; + } + + int size = 1 << length; + + int levels = size - 1; + + int s = immS & levels; + int r = immR & levels; + + if (immediate && s == levels) + { + return BitMask.Invalid; + } + + long wMask = BitUtils.FillWithOnes(s + 1); + long tMask = BitUtils.FillWithOnes(((s - r) & levels) + 1); + + if (r > 0) + { + wMask = BitUtils.RotateRight(wMask, r, size); + wMask &= BitUtils.FillWithOnes(size); + } + + return new BitMask() + { + WMask = BitUtils.Replicate(wMask, size), + TMask = BitUtils.Replicate(tMask, size), + + Pos = immS, + Shift = immR + }; + } + + public static long DecodeImm24_2(int opCode) + { + return ((long)opCode << 40) >> 38; + } + + public static long DecodeImm26_2(int opCode) + { + return ((long)opCode << 38) >> 36; + } + + public static long DecodeImmS19_2(int opCode) + { + return (((long)opCode << 40) >> 43) & ~3; + } + + public static long DecodeImmS14_2(int opCode) + { + return (((long)opCode << 45) >> 48) & ~3; + } + + public static bool VectorArgumentsInvalid(bool q, params int[] args) + { + if (q) + { + for (int i = 0; i < args.Length; i++) + { + if ((args[i] & 1) == 1) + { + return true; + } + } + } + return false; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/DecoderMode.cs b/src/ARMeilleure/Decoders/DecoderMode.cs new file mode 100644 index 00000000..55362084 --- /dev/null +++ b/src/ARMeilleure/Decoders/DecoderMode.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.Decoders +{ + enum DecoderMode + { + MultipleBlocks, + SingleBlock, + SingleInstruction, + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCode.cs b/src/ARMeilleure/Decoders/IOpCode.cs new file mode 100644 index 00000000..37ba7a4c --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode.cs @@ -0,0 +1,17 @@ +using ARMeilleure.IntermediateRepresentation; + +namespace ARMeilleure.Decoders +{ + interface IOpCode + { + ulong Address { get; } + + InstDescriptor Instruction { get; } + + RegisterSize RegisterSize { get; } + + int GetBitsCount(); + + OperandType GetOperandType(); + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCode32.cs b/src/ARMeilleure/Decoders/IOpCode32.cs new file mode 100644 index 00000000..126c1069 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32 : IOpCode + { + Condition Cond { get; } + + uint GetPc(); + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCode32Adr.cs b/src/ARMeilleure/Decoders/IOpCode32Adr.cs new file mode 100644 index 00000000..40a4f526 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32Adr.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32Adr + { + int Rd { get; } + + int Immediate { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32Alu.cs b/src/ARMeilleure/Decoders/IOpCode32Alu.cs new file mode 100644 index 00000000..69fee164 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32Alu.cs @@ -0,0 +1,8 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32Alu : IOpCode32, IOpCode32HasSetFlags + { + int Rd { get; } + int Rn { get; } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCode32AluBf.cs b/src/ARMeilleure/Decoders/IOpCode32AluBf.cs new file mode 100644 index 00000000..206c2965 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluBf.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluBf + { + int Rd { get; } + int Rn { get; } + + int Msb { get; } + int Lsb { get; } + + int SourceMask => (int)(0xFFFFFFFF >> (31 - Msb)); + int DestMask => SourceMask & (int)(0xFFFFFFFF << Lsb); + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32AluImm.cs b/src/ARMeilleure/Decoders/IOpCode32AluImm.cs new file mode 100644 index 00000000..342fb8f6 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluImm.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluImm : IOpCode32Alu + { + int Immediate { get; } + + bool IsRotated { get; } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCode32AluImm16.cs b/src/ARMeilleure/Decoders/IOpCode32AluImm16.cs new file mode 100644 index 00000000..cd128f65 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluImm16.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluImm16 : IOpCode32Alu + { + int Immediate { get; } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCode32AluMla.cs b/src/ARMeilleure/Decoders/IOpCode32AluMla.cs new file mode 100644 index 00000000..79b16425 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluMla.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluMla : IOpCode32AluReg + { + int Ra { get; } + + bool NHigh { get; } + bool MHigh { get; } + bool R { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32AluReg.cs b/src/ARMeilleure/Decoders/IOpCode32AluReg.cs new file mode 100644 index 00000000..1612cc5c --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluReg.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluReg : IOpCode32Alu + { + int Rm { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32AluRsImm.cs b/src/ARMeilleure/Decoders/IOpCode32AluRsImm.cs new file mode 100644 index 00000000..e899a659 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluRsImm.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluRsImm : IOpCode32Alu + { + int Rm { get; } + int Immediate { get; } + + ShiftType ShiftType { get; } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCode32AluRsReg.cs b/src/ARMeilleure/Decoders/IOpCode32AluRsReg.cs new file mode 100644 index 00000000..879db059 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluRsReg.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluRsReg : IOpCode32Alu + { + int Rm { get; } + int Rs { get; } + + ShiftType ShiftType { get; } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCode32AluUmull.cs b/src/ARMeilleure/Decoders/IOpCode32AluUmull.cs new file mode 100644 index 00000000..79d2bb9b --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluUmull.cs @@ -0,0 +1,13 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluUmull : IOpCode32, IOpCode32HasSetFlags + { + int RdLo { get; } + int RdHi { get; } + int Rn { get; } + int Rm { get; } + + bool NHigh { get; } + bool MHigh { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32AluUx.cs b/src/ARMeilleure/Decoders/IOpCode32AluUx.cs new file mode 100644 index 00000000..d03c7e21 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32AluUx.cs @@ -0,0 +1,8 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32AluUx : IOpCode32AluReg + { + int RotateBits { get; } + bool Add { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32BImm.cs b/src/ARMeilleure/Decoders/IOpCode32BImm.cs new file mode 100644 index 00000000..ec7db2c2 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32BImm.cs @@ -0,0 +1,4 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32BImm : IOpCode32, IOpCodeBImm { } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCode32BReg.cs b/src/ARMeilleure/Decoders/IOpCode32BReg.cs new file mode 100644 index 00000000..097ab427 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32BReg.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32BReg : IOpCode32 + { + int Rm { get; } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCode32Exception.cs b/src/ARMeilleure/Decoders/IOpCode32Exception.cs new file mode 100644 index 00000000..8f0fb81a --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32Exception.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32Exception + { + int Id { get; } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCode32HasSetFlags.cs b/src/ARMeilleure/Decoders/IOpCode32HasSetFlags.cs new file mode 100644 index 00000000..71ca6d19 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32HasSetFlags.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32HasSetFlags + { + bool? SetFlags { get; } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCode32Mem.cs b/src/ARMeilleure/Decoders/IOpCode32Mem.cs new file mode 100644 index 00000000..6664ddff --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32Mem.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32Mem : IOpCode32 + { + int Rt { get; } + int Rt2 => Rt | 1; + int Rn { get; } + + bool WBack { get; } + bool IsLoad { get; } + bool Index { get; } + bool Add { get; } + + int Immediate { get; } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCode32MemEx.cs b/src/ARMeilleure/Decoders/IOpCode32MemEx.cs new file mode 100644 index 00000000..aca7200a --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32MemEx.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32MemEx : IOpCode32Mem + { + int Rd { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32MemMult.cs b/src/ARMeilleure/Decoders/IOpCode32MemMult.cs new file mode 100644 index 00000000..4b891bc1 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32MemMult.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32MemMult : IOpCode32 + { + int Rn { get; } + + int RegisterMask { get; } + + int PostOffset { get; } + + bool IsLoad { get; } + + int Offset { get; } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCode32MemReg.cs b/src/ARMeilleure/Decoders/IOpCode32MemReg.cs new file mode 100644 index 00000000..7fe1b022 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32MemReg.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32MemReg : IOpCode32Mem + { + int Rm { get; } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCode32MemRsImm.cs b/src/ARMeilleure/Decoders/IOpCode32MemRsImm.cs new file mode 100644 index 00000000..65b7ee0b --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32MemRsImm.cs @@ -0,0 +1,8 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32MemRsImm : IOpCode32Mem + { + int Rm { get; } + ShiftType ShiftType { get; } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCode32Simd.cs b/src/ARMeilleure/Decoders/IOpCode32Simd.cs new file mode 100644 index 00000000..687254d9 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32Simd.cs @@ -0,0 +1,4 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32Simd : IOpCode32, IOpCodeSimd { } +} diff --git a/src/ARMeilleure/Decoders/IOpCode32SimdImm.cs b/src/ARMeilleure/Decoders/IOpCode32SimdImm.cs new file mode 100644 index 00000000..a0cb669c --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCode32SimdImm.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCode32SimdImm : IOpCode32Simd + { + int Vd { get; } + long Immediate { get; } + int Elems { get; } + } +} diff --git a/src/ARMeilleure/Decoders/IOpCodeAlu.cs b/src/ARMeilleure/Decoders/IOpCodeAlu.cs new file mode 100644 index 00000000..b8c28513 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeAlu.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeAlu : IOpCode + { + int Rd { get; } + int Rn { get; } + + DataOp DataOp { get; } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCodeAluImm.cs b/src/ARMeilleure/Decoders/IOpCodeAluImm.cs new file mode 100644 index 00000000..02f4c997 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeAluImm.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeAluImm : IOpCodeAlu + { + long Immediate { get; } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCodeAluRs.cs b/src/ARMeilleure/Decoders/IOpCodeAluRs.cs new file mode 100644 index 00000000..22540b11 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeAluRs.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeAluRs : IOpCodeAlu + { + int Shift { get; } + int Rm { get; } + + ShiftType ShiftType { get; } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCodeAluRx.cs b/src/ARMeilleure/Decoders/IOpCodeAluRx.cs new file mode 100644 index 00000000..9d16be78 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeAluRx.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeAluRx : IOpCodeAlu + { + int Shift { get; } + int Rm { get; } + + IntType IntType { get; } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCodeBImm.cs b/src/ARMeilleure/Decoders/IOpCodeBImm.cs new file mode 100644 index 00000000..958bff28 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeBImm.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeBImm : IOpCode + { + long Immediate { get; } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCodeCond.cs b/src/ARMeilleure/Decoders/IOpCodeCond.cs new file mode 100644 index 00000000..9808f7c0 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeCond.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeCond : IOpCode + { + Condition Cond { get; } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCodeLit.cs b/src/ARMeilleure/Decoders/IOpCodeLit.cs new file mode 100644 index 00000000..74084a45 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeLit.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeLit : IOpCode + { + int Rt { get; } + long Immediate { get; } + int Size { get; } + bool Signed { get; } + bool Prefetch { get; } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IOpCodeSimd.cs b/src/ARMeilleure/Decoders/IOpCodeSimd.cs new file mode 100644 index 00000000..056ef045 --- /dev/null +++ b/src/ARMeilleure/Decoders/IOpCodeSimd.cs @@ -0,0 +1,7 @@ +namespace ARMeilleure.Decoders +{ + interface IOpCodeSimd : IOpCode + { + int Size { get; } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/InstDescriptor.cs b/src/ARMeilleure/Decoders/InstDescriptor.cs new file mode 100644 index 00000000..577ff394 --- /dev/null +++ b/src/ARMeilleure/Decoders/InstDescriptor.cs @@ -0,0 +1,18 @@ +using ARMeilleure.Instructions; + +namespace ARMeilleure.Decoders +{ + readonly struct InstDescriptor + { + public static InstDescriptor Undefined => new InstDescriptor(InstName.Und, InstEmit.Und); + + public InstName Name { get; } + public InstEmitter Emitter { get; } + + public InstDescriptor(InstName name, InstEmitter emitter) + { + Name = name; + Emitter = emitter; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/InstEmitter.cs b/src/ARMeilleure/Decoders/InstEmitter.cs new file mode 100644 index 00000000..a8b52656 --- /dev/null +++ b/src/ARMeilleure/Decoders/InstEmitter.cs @@ -0,0 +1,6 @@ +using ARMeilleure.Translation; + +namespace ARMeilleure.Decoders +{ + delegate void InstEmitter(ArmEmitterContext context); +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/IntType.cs b/src/ARMeilleure/Decoders/IntType.cs new file mode 100644 index 00000000..244e9680 --- /dev/null +++ b/src/ARMeilleure/Decoders/IntType.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + enum IntType + { + UInt8 = 0, + UInt16 = 1, + UInt32 = 2, + UInt64 = 3, + Int8 = 4, + Int16 = 5, + Int32 = 6, + Int64 = 7 + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCode.cs b/src/ARMeilleure/Decoders/OpCode.cs new file mode 100644 index 00000000..f9aed792 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode.cs @@ -0,0 +1,49 @@ +using ARMeilleure.IntermediateRepresentation; +using System; + +namespace ARMeilleure.Decoders +{ + class OpCode : IOpCode + { + public ulong Address { get; } + public int RawOpCode { get; } + + public int OpCodeSizeInBytes { get; protected set; } = 4; + + public InstDescriptor Instruction { get; protected set; } + + public RegisterSize RegisterSize { get; protected set; } + + public static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode(inst, address, opCode); + + public OpCode(InstDescriptor inst, ulong address, int opCode) + { + Instruction = inst; + Address = address; + RawOpCode = opCode; + + RegisterSize = RegisterSize.Int64; + } + + public int GetPairsCount() => GetBitsCount() / 16; + public int GetBytesCount() => GetBitsCount() / 8; + + public int GetBitsCount() + { + switch (RegisterSize) + { + case RegisterSize.Int32: return 32; + case RegisterSize.Int64: return 64; + case RegisterSize.Simd64: return 64; + case RegisterSize.Simd128: return 128; + } + + throw new InvalidOperationException(); + } + + public OperandType GetOperandType() + { + return RegisterSize == RegisterSize.Int32 ? OperandType.I32 : OperandType.I64; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCode32.cs b/src/ARMeilleure/Decoders/OpCode32.cs new file mode 100644 index 00000000..c2f14145 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32.cs @@ -0,0 +1,34 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32 : OpCode + { + public Condition Cond { get; protected set; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32(inst, address, opCode); + + public OpCode32(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + RegisterSize = RegisterSize.Int32; + + Cond = (Condition)((uint)opCode >> 28); + } + + public bool IsThumb { get; protected init; } = false; + + public uint GetPc() + { + // Due to backwards compatibility and legacy behavior of ARMv4 CPUs pipeline, + // the PC actually points 2 instructions ahead. + if (IsThumb) + { + // PC is ahead by 4 in thumb mode whether or not the current instruction + // is 16 or 32 bit. + return (uint)Address + 4u; + } + else + { + return (uint)Address + 8u; + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCode32Alu.cs b/src/ARMeilleure/Decoders/OpCode32Alu.cs new file mode 100644 index 00000000..1625aee0 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32Alu.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32Alu : OpCode32, IOpCode32Alu + { + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32Alu(inst, address, opCode); + + public OpCode32Alu(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + + SetFlags = ((opCode >> 20) & 1) != 0; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCode32AluBf.cs b/src/ARMeilleure/Decoders/OpCode32AluBf.cs new file mode 100644 index 00000000..0cee34e6 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluBf.cs @@ -0,0 +1,22 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32AluBf : OpCode32, IOpCode32AluBf + { + public int Rd { get; } + public int Rn { get; } + + public int Msb { get; } + public int Lsb { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluBf(inst, address, opCode); + + public OpCode32AluBf(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 12) & 0xf; + Rn = (opCode >> 0) & 0xf; + + Msb = (opCode >> 16) & 0x1f; + Lsb = (opCode >> 7) & 0x1f; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluImm.cs b/src/ARMeilleure/Decoders/OpCode32AluImm.cs new file mode 100644 index 00000000..b5435aaf --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluImm.cs @@ -0,0 +1,23 @@ +using ARMeilleure.Common; + +namespace ARMeilleure.Decoders +{ + class OpCode32AluImm : OpCode32Alu, IOpCode32AluImm + { + public int Immediate { get; } + + public bool IsRotated { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluImm(inst, address, opCode); + + public OpCode32AluImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int value = (opCode >> 0) & 0xff; + int shift = (opCode >> 8) & 0xf; + + Immediate = BitUtils.RotateRight(value, shift * 2, 32); + + IsRotated = shift != 0; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCode32AluImm16.cs b/src/ARMeilleure/Decoders/OpCode32AluImm16.cs new file mode 100644 index 00000000..e24edeb4 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluImm16.cs @@ -0,0 +1,17 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32AluImm16 : OpCode32Alu, IOpCode32AluImm16 + { + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluImm16(inst, address, opCode); + + public OpCode32AluImm16(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int imm12 = opCode & 0xfff; + int imm4 = (opCode >> 16) & 0xf; + + Immediate = (imm4 << 12) | imm12; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluMla.cs b/src/ARMeilleure/Decoders/OpCode32AluMla.cs new file mode 100644 index 00000000..2cd2b9dc --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluMla.cs @@ -0,0 +1,30 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32AluMla : OpCode32, IOpCode32AluMla + { + public int Rn { get; } + public int Rm { get; } + public int Ra { get; } + public int Rd { get; } + + public bool NHigh { get; } + public bool MHigh { get; } + public bool R { get; } + public bool? SetFlags { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluMla(inst, address, opCode); + + public OpCode32AluMla(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rn = (opCode >> 0) & 0xf; + Rm = (opCode >> 8) & 0xf; + Ra = (opCode >> 12) & 0xf; + Rd = (opCode >> 16) & 0xf; + R = (opCode & (1 << 5)) != 0; + + NHigh = ((opCode >> 5) & 0x1) == 1; + MHigh = ((opCode >> 6) & 0x1) == 1; + SetFlags = ((opCode >> 20) & 1) != 0; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluReg.cs b/src/ARMeilleure/Decoders/OpCode32AluReg.cs new file mode 100644 index 00000000..493a977f --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluReg.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32AluReg : OpCode32Alu, IOpCode32AluReg + { + public int Rm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluReg(inst, address, opCode); + + public OpCode32AluReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluRsImm.cs b/src/ARMeilleure/Decoders/OpCode32AluRsImm.cs new file mode 100644 index 00000000..c2dee6c9 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluRsImm.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32AluRsImm : OpCode32Alu, IOpCode32AluRsImm + { + public int Rm { get; } + public int Immediate { get; } + + public ShiftType ShiftType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluRsImm(inst, address, opCode); + + public OpCode32AluRsImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + Immediate = (opCode >> 7) & 0x1f; + + ShiftType = (ShiftType)((opCode >> 5) & 3); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCode32AluRsReg.cs b/src/ARMeilleure/Decoders/OpCode32AluRsReg.cs new file mode 100644 index 00000000..04740d08 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluRsReg.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32AluRsReg : OpCode32Alu, IOpCode32AluRsReg + { + public int Rm { get; } + public int Rs { get; } + + public ShiftType ShiftType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluRsReg(inst, address, opCode); + + public OpCode32AluRsReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + Rs = (opCode >> 8) & 0xf; + + ShiftType = (ShiftType)((opCode >> 5) & 3); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluUmull.cs b/src/ARMeilleure/Decoders/OpCode32AluUmull.cs new file mode 100644 index 00000000..bf80df3f --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluUmull.cs @@ -0,0 +1,30 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32AluUmull : OpCode32, IOpCode32AluUmull + { + public int RdLo { get; } + public int RdHi { get; } + public int Rn { get; } + public int Rm { get; } + + public bool NHigh { get; } + public bool MHigh { get; } + + public bool? SetFlags { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluUmull(inst, address, opCode); + + public OpCode32AluUmull(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + RdLo = (opCode >> 12) & 0xf; + RdHi = (opCode >> 16) & 0xf; + Rm = (opCode >> 8) & 0xf; + Rn = (opCode >> 0) & 0xf; + + NHigh = ((opCode >> 5) & 0x1) == 1; + MHigh = ((opCode >> 6) & 0x1) == 1; + + SetFlags = ((opCode >> 20) & 0x1) != 0; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32AluUx.cs b/src/ARMeilleure/Decoders/OpCode32AluUx.cs new file mode 100644 index 00000000..57068675 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32AluUx.cs @@ -0,0 +1,18 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCode32AluUx : OpCode32AluReg, IOpCode32AluUx + { + public int Rotate { get; } + public int RotateBits => Rotate * 8; + public bool Add => Rn != RegisterAlias.Aarch32Pc; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32AluUx(inst, address, opCode); + + public OpCode32AluUx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rotate = (opCode >> 10) & 0x3; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32BImm.cs b/src/ARMeilleure/Decoders/OpCode32BImm.cs new file mode 100644 index 00000000..f2959b33 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32BImm.cs @@ -0,0 +1,29 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32BImm : OpCode32, IOpCode32BImm + { + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32BImm(inst, address, opCode); + + public OpCode32BImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + uint pc = GetPc(); + + // When the condition is never, the instruction is BLX to Thumb mode. + if (Cond != Condition.Nv) + { + pc &= ~3u; + } + + Immediate = pc + DecoderHelper.DecodeImm24_2(opCode); + + if (Cond == Condition.Nv) + { + long H = (opCode >> 23) & 2; + + Immediate |= H; + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCode32BReg.cs b/src/ARMeilleure/Decoders/OpCode32BReg.cs new file mode 100644 index 00000000..d4f5f760 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32BReg.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32BReg : OpCode32, IOpCode32BReg + { + public int Rm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32BReg(inst, address, opCode); + + public OpCode32BReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = opCode & 0xf; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCode32Exception.cs b/src/ARMeilleure/Decoders/OpCode32Exception.cs new file mode 100644 index 00000000..b4edcc10 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32Exception.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32Exception : OpCode32, IOpCode32Exception + { + public int Id { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32Exception(inst, address, opCode); + + public OpCode32Exception(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Id = opCode & 0xFFFFFF; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32Mem.cs b/src/ARMeilleure/Decoders/OpCode32Mem.cs new file mode 100644 index 00000000..ceb1e49f --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32Mem.cs @@ -0,0 +1,39 @@ +using ARMeilleure.Instructions; + +namespace ARMeilleure.Decoders +{ + class OpCode32Mem : OpCode32, IOpCode32Mem + { + public int Rt { get; protected set; } + public int Rn { get; } + + public int Immediate { get; protected set; } + + public bool Index { get; } + public bool Add { get; } + public bool WBack { get; } + public bool Unprivileged { get; } + + public bool IsLoad { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32Mem(inst, address, opCode); + + public OpCode32Mem(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + + bool isLoad = (opCode & (1 << 20)) != 0; + bool w = (opCode & (1 << 21)) != 0; + bool u = (opCode & (1 << 23)) != 0; + bool p = (opCode & (1 << 24)) != 0; + + Index = p; + Add = u; + WBack = !p || w; + Unprivileged = !p && w; + + IsLoad = isLoad || inst.Name == InstName.Ldrd; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCode32MemImm.cs b/src/ARMeilleure/Decoders/OpCode32MemImm.cs new file mode 100644 index 00000000..3af4b6f7 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MemImm.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32MemImm : OpCode32Mem + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MemImm(inst, address, opCode); + + public OpCode32MemImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Immediate = opCode & 0xfff; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCode32MemImm8.cs b/src/ARMeilleure/Decoders/OpCode32MemImm8.cs new file mode 100644 index 00000000..1b8a57de --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MemImm8.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32MemImm8 : OpCode32Mem + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MemImm8(inst, address, opCode); + + public OpCode32MemImm8(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int imm4L = (opCode >> 0) & 0xf; + int imm4H = (opCode >> 8) & 0xf; + + Immediate = imm4L | (imm4H << 4); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCode32MemLdEx.cs b/src/ARMeilleure/Decoders/OpCode32MemLdEx.cs new file mode 100644 index 00000000..520113f4 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MemLdEx.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32MemLdEx : OpCode32Mem, IOpCode32MemEx + { + public int Rd { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MemLdEx(inst, address, opCode); + + public OpCode32MemLdEx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = opCode & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32MemMult.cs b/src/ARMeilleure/Decoders/OpCode32MemMult.cs new file mode 100644 index 00000000..522b96bb --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MemMult.cs @@ -0,0 +1,52 @@ +using System.Numerics; + +namespace ARMeilleure.Decoders +{ + class OpCode32MemMult : OpCode32, IOpCode32MemMult + { + public int Rn { get; } + + public int RegisterMask { get; } + public int Offset { get; } + public int PostOffset { get; } + + public bool IsLoad { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MemMult(inst, address, opCode); + + public OpCode32MemMult(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rn = (opCode >> 16) & 0xf; + + bool isLoad = (opCode & (1 << 20)) != 0; + bool w = (opCode & (1 << 21)) != 0; + bool u = (opCode & (1 << 23)) != 0; + bool p = (opCode & (1 << 24)) != 0; + + RegisterMask = opCode & 0xffff; + + int regsSize = BitOperations.PopCount((uint)RegisterMask) * 4; + + if (!u) + { + Offset -= regsSize; + } + + if (u == p) + { + Offset += 4; + } + + if (w) + { + PostOffset = u ? regsSize : -regsSize; + } + else + { + PostOffset = 0; + } + + IsLoad = isLoad; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCode32MemReg.cs b/src/ARMeilleure/Decoders/OpCode32MemReg.cs new file mode 100644 index 00000000..786f37fa --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MemReg.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32MemReg : OpCode32Mem, IOpCode32MemReg + { + public int Rm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MemReg(inst, address, opCode); + + public OpCode32MemReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32MemRsImm.cs b/src/ARMeilleure/Decoders/OpCode32MemRsImm.cs new file mode 100644 index 00000000..e1284cf7 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MemRsImm.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32MemRsImm : OpCode32Mem, IOpCode32MemRsImm + { + public int Rm { get; } + public ShiftType ShiftType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MemRsImm(inst, address, opCode); + + public OpCode32MemRsImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + Immediate = (opCode >> 7) & 0x1f; + + ShiftType = (ShiftType)((opCode >> 5) & 3); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32MemStEx.cs b/src/ARMeilleure/Decoders/OpCode32MemStEx.cs new file mode 100644 index 00000000..dcf93b22 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MemStEx.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32MemStEx : OpCode32Mem, IOpCode32MemEx + { + public int Rd { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MemStEx(inst, address, opCode); + + public OpCode32MemStEx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 12) & 0xf; + Rt = (opCode >> 0) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32Mrs.cs b/src/ARMeilleure/Decoders/OpCode32Mrs.cs new file mode 100644 index 00000000..c34a8b99 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32Mrs.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32Mrs : OpCode32 + { + public bool R { get; } + public int Rd { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32Mrs(inst, address, opCode); + + public OpCode32Mrs(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + R = ((opCode >> 22) & 1) != 0; + Rd = (opCode >> 12) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32MsrReg.cs b/src/ARMeilleure/Decoders/OpCode32MsrReg.cs new file mode 100644 index 00000000..d897ffd8 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32MsrReg.cs @@ -0,0 +1,29 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCode32MsrReg : OpCode32 + { + public bool R { get; } + public int Mask { get; } + public int Rd { get; } + public bool Banked { get; } + public int Rn { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32MsrReg(inst, address, opCode); + + public OpCode32MsrReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + R = ((opCode >> 22) & 1) != 0; + Mask = (opCode >> 16) & 0xf; + Rd = (opCode >> 12) & 0xf; + Banked = ((opCode >> 9) & 1) != 0; + Rn = (opCode >> 0) & 0xf; + + if (Rn == RegisterAlias.Aarch32Pc || Mask == 0) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32Sat.cs b/src/ARMeilleure/Decoders/OpCode32Sat.cs new file mode 100644 index 00000000..621def27 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32Sat.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32Sat : OpCode32 + { + public int Rn { get; } + public int Imm5 { get; } + public int Rd { get; } + public int SatImm { get; } + + public ShiftType ShiftType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32Sat(inst, address, opCode); + + public OpCode32Sat(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rn = (opCode >> 0) & 0xf; + Imm5 = (opCode >> 7) & 0x1f; + Rd = (opCode >> 12) & 0xf; + SatImm = (opCode >> 16) & 0x1f; + + ShiftType = (ShiftType)((opCode >> 5) & 2); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCode32Sat16.cs b/src/ARMeilleure/Decoders/OpCode32Sat16.cs new file mode 100644 index 00000000..51061b07 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32Sat16.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32Sat16 : OpCode32 + { + public int Rn { get; } + public int Rd { get; } + public int SatImm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32Sat16(inst, address, opCode); + + public OpCode32Sat16(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rn = (opCode >> 0) & 0xf; + Rd = (opCode >> 12) & 0xf; + SatImm = (opCode >> 16) & 0xf; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCode32Simd.cs b/src/ARMeilleure/Decoders/OpCode32Simd.cs new file mode 100644 index 00000000..636aa0a8 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32Simd.cs @@ -0,0 +1,33 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32Simd : OpCode32SimdBase + { + public int Opc { get; protected set; } + public bool Q { get; protected set; } + public bool F { get; protected set; } + public bool U { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32Simd(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32Simd(inst, address, opCode, true); + + public OpCode32Simd(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Size = (opCode >> 20) & 0x3; + Q = ((opCode >> 6) & 0x1) != 0; + F = ((opCode >> 10) & 0x1) != 0; + U = ((opCode >> (isThumb ? 28 : 24)) & 0x1) != 0; + Opc = (opCode >> 7) & 0x3; + + RegisterSize = Q ? RegisterSize.Simd128 : RegisterSize.Simd64; + + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + Vm = ((opCode >> 1) & 0x10) | ((opCode >> 0) & 0xf); + + // Subclasses have their own handling of Vx to account for before checking. + if (GetType() == typeof(OpCode32Simd) && DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdBase.cs b/src/ARMeilleure/Decoders/OpCode32SimdBase.cs new file mode 100644 index 00000000..4382fc2a --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdBase.cs @@ -0,0 +1,55 @@ +using System; + +namespace ARMeilleure.Decoders +{ + abstract class OpCode32SimdBase : OpCode32, IOpCode32Simd + { + public int Vd { get; protected set; } + public int Vm { get; protected set; } + public int Size { get; protected set; } + + // Helpers to index doublewords within quad words. Essentially, looping over the vector starts at quadword Q and index Fx or Ix within it, + // depending on instruction type. + // + // Qx: The quadword register that the target vector is contained in. + // Ix: The starting index of the target vector within the quadword, with size treated as integer. + // Fx: The starting index of the target vector within the quadword, with size treated as floating point. (16 or 32) + public int Qd => GetQuadwordIndex(Vd); + public int Id => GetQuadwordSubindex(Vd) << (3 - Size); + public int Fd => GetQuadwordSubindex(Vd) << (1 - (Size & 1)); // When the top bit is truncated, 1 is fp16 which is an optional extension in ARMv8.2. We always assume 64. + + public int Qm => GetQuadwordIndex(Vm); + public int Im => GetQuadwordSubindex(Vm) << (3 - Size); + public int Fm => GetQuadwordSubindex(Vm) << (1 - (Size & 1)); + + protected int GetQuadwordIndex(int index) + { + switch (RegisterSize) + { + case RegisterSize.Simd128: + case RegisterSize.Simd64: + return index >> 1; + } + + throw new InvalidOperationException(); + } + + protected int GetQuadwordSubindex(int index) + { + switch (RegisterSize) + { + case RegisterSize.Simd128: + return 0; + case RegisterSize.Simd64: + return index & 1; + } + + throw new InvalidOperationException(); + } + + protected OpCode32SimdBase(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdBinary.cs b/src/ARMeilleure/Decoders/OpCode32SimdBinary.cs new file mode 100644 index 00000000..ba190de9 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdBinary.cs @@ -0,0 +1,21 @@ +namespace ARMeilleure.Decoders +{ + /// + /// A special alias that always runs in 64 bit int, to speed up binary ops a little. + /// + class OpCode32SimdBinary : OpCode32SimdReg + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdBinary(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdBinary(inst, address, opCode, true); + + public OpCode32SimdBinary(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Size = 3; + + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm, Vn)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdCmpZ.cs b/src/ARMeilleure/Decoders/OpCode32SimdCmpZ.cs new file mode 100644 index 00000000..445e6781 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdCmpZ.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdCmpZ : OpCode32Simd + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCmpZ(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCmpZ(inst, address, opCode, true); + + public OpCode32SimdCmpZ(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Size = (opCode >> 18) & 0x3; + + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdCvtFI.cs b/src/ARMeilleure/Decoders/OpCode32SimdCvtFI.cs new file mode 100644 index 00000000..41cf4d88 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdCvtFI.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdCvtFI : OpCode32SimdS + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCvtFI(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCvtFI(inst, address, opCode, true); + + public OpCode32SimdCvtFI(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Opc = (opCode >> 7) & 0x1; + + bool toInteger = (Opc2 & 0b100) != 0; + + if (toInteger) + { + Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e); + } + else + { + Vm = ((opCode >> 5) & 0x1) | ((opCode << 1) & 0x1e); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdCvtTB.cs b/src/ARMeilleure/Decoders/OpCode32SimdCvtTB.cs new file mode 100644 index 00000000..a95b32ab --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdCvtTB.cs @@ -0,0 +1,44 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdCvtTB : OpCode32, IOpCode32Simd + { + public int Vd { get; } + public int Vm { get; } + public bool Op { get; } // Convert to Half / Convert from Half + public bool T { get; } // Top / Bottom + public int Size { get; } // Double / Single + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCvtTB(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdCvtTB(inst, address, opCode, true); + + public OpCode32SimdCvtTB(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Op = ((opCode >> 16) & 0x1) != 0; + T = ((opCode >> 7) & 0x1) != 0; + Size = ((opCode >> 8) & 0x1); + + RegisterSize = Size == 1 ? RegisterSize.Int64 : RegisterSize.Int32; + + if (Size == 1) + { + if (Op) + { + Vm = ((opCode >> 1) & 0x10) | ((opCode >> 0) & 0xf); + Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e); + } + else + { + Vm = ((opCode >> 5) & 0x1) | ((opCode << 1) & 0x1e); + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + } + } + else + { + Vm = ((opCode >> 5) & 0x1) | ((opCode << 1) & 0x1e); + Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e); + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCode32SimdDupElem.cs b/src/ARMeilleure/Decoders/OpCode32SimdDupElem.cs new file mode 100644 index 00000000..c455b5b4 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdDupElem.cs @@ -0,0 +1,43 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdDupElem : OpCode32Simd + { + public int Index { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdDupElem(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdDupElem(inst, address, opCode, true); + + public OpCode32SimdDupElem(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + var opc = (opCode >> 16) & 0xf; + + if ((opc & 0b1) == 1) + { + Size = 0; + Index = (opc >> 1) & 0x7; + } + else if ((opc & 0b11) == 0b10) + { + Size = 1; + Index = (opc >> 2) & 0x3; + } + else if ((opc & 0b111) == 0b100) + { + Size = 2; + Index = (opc >> 3) & 0x1; + } + else + { + Instruction = InstDescriptor.Undefined; + } + + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + Vm = ((opCode >> 1) & 0x10) | ((opCode >> 0) & 0xf); + + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdDupGP.cs b/src/ARMeilleure/Decoders/OpCode32SimdDupGP.cs new file mode 100644 index 00000000..31546ea3 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdDupGP.cs @@ -0,0 +1,36 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdDupGP : OpCode32, IOpCode32Simd + { + public int Size { get; } + public int Vd { get; } + public int Rt { get; } + public bool Q { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdDupGP(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdDupGP(inst, address, opCode, true); + + public OpCode32SimdDupGP(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Size = 2 - (((opCode >> 21) & 0x2) | ((opCode >> 5) & 0x1)); // B:E - 0 for 32, 16 then 8. + if (Size == -1) + { + Instruction = InstDescriptor.Undefined; + return; + } + Q = ((opCode >> 21) & 0x1) != 0; + + RegisterSize = Q ? RegisterSize.Simd128 : RegisterSize.Simd64; + + Vd = ((opCode >> 3) & 0x10) | ((opCode >> 16) & 0xf); + Rt = ((opCode >> 12) & 0xf); + + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdExt.cs b/src/ARMeilleure/Decoders/OpCode32SimdExt.cs new file mode 100644 index 00000000..6dbb5b66 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdExt.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdExt : OpCode32SimdReg + { + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdExt(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdExt(inst, address, opCode, true); + + public OpCode32SimdExt(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Immediate = (opCode >> 8) & 0xf; + Size = 0; + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm, Vn) || (!Q && Immediate > 7)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdImm.cs b/src/ARMeilleure/Decoders/OpCode32SimdImm.cs new file mode 100644 index 00000000..bf0ca527 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdImm.cs @@ -0,0 +1,38 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdImm : OpCode32SimdBase, IOpCode32SimdImm + { + public bool Q { get; } + public long Immediate { get; } + public int Elems => GetBytesCount() >> Size; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdImm(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdImm(inst, address, opCode, true); + + public OpCode32SimdImm(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Vd = (opCode >> 12) & 0xf; + Vd |= (opCode >> 18) & 0x10; + + Q = ((opCode >> 6) & 0x1) > 0; + + int cMode = (opCode >> 8) & 0xf; + int op = (opCode >> 5) & 0x1; + + long imm; + + imm = ((uint)opCode >> 0) & 0xf; + imm |= ((uint)opCode >> 12) & 0x70; + imm |= ((uint)opCode >> (isThumb ? 21 : 17)) & 0x80; + + (Immediate, Size) = OpCodeSimdHelper.GetSimdImmediateAndSize(cMode, op, imm); + + RegisterSize = Q ? RegisterSize.Simd128 : RegisterSize.Simd64; + + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdImm44.cs b/src/ARMeilleure/Decoders/OpCode32SimdImm44.cs new file mode 100644 index 00000000..fa00a935 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdImm44.cs @@ -0,0 +1,41 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdImm44 : OpCode32, IOpCode32SimdImm + { + public int Vd { get; } + public long Immediate { get; } + public int Size { get; } + public int Elems { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdImm44(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdImm44(inst, address, opCode, true); + + public OpCode32SimdImm44(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Size = (opCode >> 8) & 0x3; + + bool single = Size != 3; + + if (single) + { + Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e); + } + else + { + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + } + + long imm; + + imm = ((uint)opCode >> 0) & 0xf; + imm |= ((uint)opCode >> 12) & 0xf0; + + Immediate = (Size == 3) ? (long)DecoderHelper.Imm8ToFP64Table[(int)imm] : DecoderHelper.Imm8ToFP32Table[(int)imm]; + + RegisterSize = (!single) ? RegisterSize.Int64 : RegisterSize.Int32; + Elems = 1; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdLong.cs b/src/ARMeilleure/Decoders/OpCode32SimdLong.cs new file mode 100644 index 00000000..8d64d673 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdLong.cs @@ -0,0 +1,30 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdLong : OpCode32SimdBase + { + public bool U { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdLong(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdLong(inst, address, opCode, true); + + public OpCode32SimdLong(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + int imm3h = (opCode >> 19) & 0x7; + + // The value must be a power of 2, otherwise it is the encoding of another instruction. + switch (imm3h) + { + case 1: Size = 0; break; + case 2: Size = 1; break; + case 4: Size = 2; break; + } + + U = ((opCode >> (isThumb ? 28 : 24)) & 0x1) != 0; + + RegisterSize = RegisterSize.Simd64; + + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + Vm = ((opCode >> 1) & 0x10) | ((opCode >> 0) & 0xf); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMemImm.cs b/src/ARMeilleure/Decoders/OpCode32SimdMemImm.cs new file mode 100644 index 00000000..c933a5ad --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMemImm.cs @@ -0,0 +1,40 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMemImm : OpCode32, IOpCode32Simd + { + public int Vd { get; } + public int Rn { get; } + public int Size { get; } + public bool Add { get; } + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemImm(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemImm(inst, address, opCode, true); + + public OpCode32SimdMemImm(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Immediate = opCode & 0xff; + + Rn = (opCode >> 16) & 0xf; + Size = (opCode >> 8) & 0x3; + + Immediate <<= (Size == 1) ? 1 : 2; + + bool u = (opCode & (1 << 23)) != 0; + Add = u; + + bool single = Size != 3; + + if (single) + { + Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e); + } + else + { + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMemMult.cs b/src/ARMeilleure/Decoders/OpCode32SimdMemMult.cs new file mode 100644 index 00000000..a16a03d3 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMemMult.cs @@ -0,0 +1,76 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMemMult : OpCode32 + { + public int Rn { get; } + public int Vd { get; } + + public int RegisterRange { get; } + public int Offset { get; } + public int PostOffset { get; } + public bool IsLoad { get; } + public bool DoubleWidth { get; } + public bool Add { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemMult(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemMult(inst, address, opCode, true); + + public OpCode32SimdMemMult(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Rn = (opCode >> 16) & 0xf; + + bool isLoad = (opCode & (1 << 20)) != 0; + bool w = (opCode & (1 << 21)) != 0; + bool u = (opCode & (1 << 23)) != 0; + bool p = (opCode & (1 << 24)) != 0; + + if (p == u && w) + { + Instruction = InstDescriptor.Undefined; + return; + } + + DoubleWidth = (opCode & (1 << 8)) != 0; + + if (!DoubleWidth) + { + Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e); + } + else + { + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + } + + Add = u; + + RegisterRange = opCode & 0xff; + + int regsSize = RegisterRange * 4; // Double mode is still measured in single register size. + + if (!u) + { + Offset -= regsSize; + } + + if (w) + { + PostOffset = u ? regsSize : -regsSize; + } + else + { + PostOffset = 0; + } + + IsLoad = isLoad; + + int regs = DoubleWidth ? RegisterRange / 2 : RegisterRange; + + if (RegisterRange == 0 || RegisterRange > 32 || Vd + regs > 32) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMemPair.cs b/src/ARMeilleure/Decoders/OpCode32SimdMemPair.cs new file mode 100644 index 00000000..da88eed2 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMemPair.cs @@ -0,0 +1,50 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMemPair : OpCode32, IOpCode32Simd + { + private static int[] _regsMap = + { + 1, 1, 4, 2, + 1, 1, 3, 1, + 1, 1, 2, 1, + 1, 1, 1, 1 + }; + + public int Vd { get; } + public int Rn { get; } + public int Rm { get; } + public int Align { get; } + public bool WBack { get; } + public bool RegisterIndex { get; } + public int Size { get; } + public int Elems => 8 >> Size; + public int Regs { get; } + public int Increment { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemPair(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemPair(inst, address, opCode, true); + + public OpCode32SimdMemPair(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Vd = (opCode >> 12) & 0xf; + Vd |= (opCode >> 18) & 0x10; + + Size = (opCode >> 6) & 0x3; + + Align = (opCode >> 4) & 0x3; + Rm = (opCode >> 0) & 0xf; + Rn = (opCode >> 16) & 0xf; + + WBack = Rm != RegisterAlias.Aarch32Pc; + RegisterIndex = Rm != RegisterAlias.Aarch32Pc && Rm != RegisterAlias.Aarch32Sp; + + Regs = _regsMap[(opCode >> 8) & 0xf]; + + Increment = ((opCode >> 8) & 0x1) + 1; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMemSingle.cs b/src/ARMeilleure/Decoders/OpCode32SimdMemSingle.cs new file mode 100644 index 00000000..35dd41c2 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMemSingle.cs @@ -0,0 +1,51 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMemSingle : OpCode32, IOpCode32Simd + { + public int Vd { get; } + public int Rn { get; } + public int Rm { get; } + public int IndexAlign { get; } + public int Index { get; } + public bool WBack { get; } + public bool RegisterIndex { get; } + public int Size { get; } + public bool Replicate { get; } + public int Increment { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemSingle(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMemSingle(inst, address, opCode, true); + + public OpCode32SimdMemSingle(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Vd = (opCode >> 12) & 0xf; + Vd |= (opCode >> 18) & 0x10; + + IndexAlign = (opCode >> 4) & 0xf; + + Size = (opCode >> 10) & 0x3; + Replicate = Size == 3; + if (Replicate) + { + Size = (opCode >> 6) & 0x3; + Increment = ((opCode >> 5) & 1) + 1; + Index = 0; + } + else + { + Increment = (((IndexAlign >> Size) & 1) == 0) ? 1 : 2; + Index = IndexAlign >> (1 + Size); + } + + Rm = (opCode >> 0) & 0xf; + Rn = (opCode >> 16) & 0xf; + + WBack = Rm != RegisterAlias.Aarch32Pc; + RegisterIndex = Rm != RegisterAlias.Aarch32Pc && Rm != RegisterAlias.Aarch32Sp; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMovGp.cs b/src/ARMeilleure/Decoders/OpCode32SimdMovGp.cs new file mode 100644 index 00000000..5afd3488 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMovGp.cs @@ -0,0 +1,31 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMovGp : OpCode32, IOpCode32Simd + { + public int Size => 2; + + public int Vn { get; } + public int Rt { get; } + public int Op { get; } + + public int Opc1 { get; } + public int Opc2 { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovGp(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovGp(inst, address, opCode, true); + + public OpCode32SimdMovGp(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + // Which one is used is instruction dependant. + Op = (opCode >> 20) & 0x1; + + Opc1 = (opCode >> 21) & 0x3; + Opc2 = (opCode >> 5) & 0x3; + + Vn = ((opCode >> 7) & 0x1) | ((opCode >> 15) & 0x1e); + Rt = (opCode >> 12) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMovGpDouble.cs b/src/ARMeilleure/Decoders/OpCode32SimdMovGpDouble.cs new file mode 100644 index 00000000..2d693119 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMovGpDouble.cs @@ -0,0 +1,36 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMovGpDouble : OpCode32, IOpCode32Simd + { + public int Size => 3; + + public int Vm { get; } + public int Rt { get; } + public int Rt2 { get; } + public int Op { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovGpDouble(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovGpDouble(inst, address, opCode, true); + + public OpCode32SimdMovGpDouble(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + // Which one is used is instruction dependant. + Op = (opCode >> 20) & 0x1; + + Rt = (opCode >> 12) & 0xf; + Rt2 = (opCode >> 16) & 0xf; + + bool single = (opCode & (1 << 8)) == 0; + if (single) + { + Vm = ((opCode >> 5) & 0x1) | ((opCode << 1) & 0x1e); + } + else + { + Vm = ((opCode >> 1) & 0x10) | ((opCode >> 0) & 0xf); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMovGpElem.cs b/src/ARMeilleure/Decoders/OpCode32SimdMovGpElem.cs new file mode 100644 index 00000000..7816665f --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMovGpElem.cs @@ -0,0 +1,51 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMovGpElem : OpCode32, IOpCode32Simd + { + public int Size { get; } + + public int Vd { get; } + public int Rt { get; } + public int Op { get; } + public bool U { get; } + + public int Index { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovGpElem(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovGpElem(inst, address, opCode, true); + + public OpCode32SimdMovGpElem(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Op = (opCode >> 20) & 0x1; + U = ((opCode >> 23) & 1) != 0; + + var opc = (((opCode >> 23) & 1) << 4) | (((opCode >> 21) & 0x3) << 2) | ((opCode >> 5) & 0x3); + + if ((opc & 0b01000) == 0b01000) + { + Size = 0; + Index = opc & 0x7; + } + else if ((opc & 0b01001) == 0b00001) + { + Size = 1; + Index = (opc >> 1) & 0x3; + } + else if ((opc & 0b11011) == 0) + { + Size = 2; + Index = (opc >> 2) & 0x1; + } + else + { + Instruction = InstDescriptor.Undefined; + return; + } + + Vd = ((opCode >> 3) & 0x10) | ((opCode >> 16) & 0xf); + Rt = (opCode >> 12) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdMovn.cs b/src/ARMeilleure/Decoders/OpCode32SimdMovn.cs new file mode 100644 index 00000000..576e12cc --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdMovn.cs @@ -0,0 +1,13 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdMovn : OpCode32Simd + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovn(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdMovn(inst, address, opCode, true); + + public OpCode32SimdMovn(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Size = (opCode >> 18) & 0x3; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdReg.cs b/src/ARMeilleure/Decoders/OpCode32SimdReg.cs new file mode 100644 index 00000000..1c46b0e0 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdReg.cs @@ -0,0 +1,25 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdReg : OpCode32Simd + { + public int Vn { get; } + + public int Qn => GetQuadwordIndex(Vn); + public int In => GetQuadwordSubindex(Vn) << (3 - Size); + public int Fn => GetQuadwordSubindex(Vn) << (1 - (Size & 1)); + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdReg(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdReg(inst, address, opCode, true); + + public OpCode32SimdReg(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Vn = ((opCode >> 3) & 0x10) | ((opCode >> 16) & 0xf); + + // Subclasses have their own handling of Vx to account for before checking. + if (GetType() == typeof(OpCode32SimdReg) && DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm, Vn)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdRegElem.cs b/src/ARMeilleure/Decoders/OpCode32SimdRegElem.cs new file mode 100644 index 00000000..173c5265 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdRegElem.cs @@ -0,0 +1,31 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdRegElem : OpCode32SimdReg + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegElem(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegElem(inst, address, opCode, true); + + public OpCode32SimdRegElem(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Q = ((opCode >> (isThumb ? 28 : 24)) & 0x1) != 0; + F = ((opCode >> 8) & 0x1) != 0; + Size = (opCode >> 20) & 0x3; + + RegisterSize = Q ? RegisterSize.Simd128 : RegisterSize.Simd64; + + if (Size == 1) + { + Vm = ((opCode >> 3) & 0x1) | ((opCode >> 4) & 0x2) | ((opCode << 2) & 0x1c); + } + else /* if (Size == 2) */ + { + Vm = ((opCode >> 5) & 0x1) | ((opCode << 1) & 0x1e); + } + + if (GetType() == typeof(OpCode32SimdRegElem) && DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vn) || Size == 0 || (Size == 1 && F)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdRegElemLong.cs b/src/ARMeilleure/Decoders/OpCode32SimdRegElemLong.cs new file mode 100644 index 00000000..b87ac413 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdRegElemLong.cs @@ -0,0 +1,22 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdRegElemLong : OpCode32SimdRegElem + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegElemLong(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegElemLong(inst, address, opCode, true); + + public OpCode32SimdRegElemLong(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Q = false; + F = false; + + RegisterSize = RegisterSize.Simd64; + + // (Vd & 1) != 0 || Size == 3 are also invalid, but they are checked on encoding. + if (Size == 0) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdRegLong.cs b/src/ARMeilleure/Decoders/OpCode32SimdRegLong.cs new file mode 100644 index 00000000..11069383 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdRegLong.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdRegLong : OpCode32SimdReg + { + public bool Polynomial { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegLong(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegLong(inst, address, opCode, true); + + public OpCode32SimdRegLong(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Q = false; + RegisterSize = RegisterSize.Simd64; + + Polynomial = ((opCode >> 9) & 0x1) != 0; + + // Subclasses have their own handling of Vx to account for before checking. + if (GetType() == typeof(OpCode32SimdRegLong) && DecoderHelper.VectorArgumentsInvalid(true, Vd)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdRegS.cs b/src/ARMeilleure/Decoders/OpCode32SimdRegS.cs new file mode 100644 index 00000000..8168e83f --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdRegS.cs @@ -0,0 +1,23 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdRegS : OpCode32SimdS + { + public int Vn { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegS(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegS(inst, address, opCode, true); + + public OpCode32SimdRegS(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + bool single = Size != 3; + if (single) + { + Vn = ((opCode >> 7) & 0x1) | ((opCode >> 15) & 0x1e); + } + else + { + Vn = ((opCode >> 3) & 0x10) | ((opCode >> 16) & 0xf); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdRegWide.cs b/src/ARMeilleure/Decoders/OpCode32SimdRegWide.cs new file mode 100644 index 00000000..fd2b3bf1 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdRegWide.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdRegWide : OpCode32SimdReg + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegWide(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRegWide(inst, address, opCode, true); + + public OpCode32SimdRegWide(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Q = false; + RegisterSize = RegisterSize.Simd64; + + // Subclasses have their own handling of Vx to account for before checking. + if (GetType() == typeof(OpCode32SimdRegWide) && DecoderHelper.VectorArgumentsInvalid(true, Vd, Vn)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdRev.cs b/src/ARMeilleure/Decoders/OpCode32SimdRev.cs new file mode 100644 index 00000000..cb64765f --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdRev.cs @@ -0,0 +1,23 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdRev : OpCode32SimdCmpZ + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRev(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdRev(inst, address, opCode, true); + + public OpCode32SimdRev(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + if (Opc + Size >= 3) + { + Instruction = InstDescriptor.Undefined; + return; + } + + // Currently, this instruction is treated as though it's OPCODE is the true size, + // which lets us deal with reversing vectors on a single element basis (eg. math magic an I64 rather than insert lots of I8s). + int tempSize = Size; + Size = 3 - Opc; // Op 0 is 64 bit, 1 is 32 and so on. + Opc = tempSize; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdS.cs b/src/ARMeilleure/Decoders/OpCode32SimdS.cs new file mode 100644 index 00000000..63c03c01 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdS.cs @@ -0,0 +1,39 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdS : OpCode32, IOpCode32Simd + { + public int Vd { get; protected set; } + public int Vm { get; protected set; } + public int Opc { get; protected set; } // "with_zero" (Opc<1>) [Vcmp, Vcmpe]. + public int Opc2 { get; } // opc2 or RM (opc2<1:0>) [Vcvt, Vrint]. + public int Size { get; protected set; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdS(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdS(inst, address, opCode, true); + + public OpCode32SimdS(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Opc = (opCode >> 15) & 0x3; + Opc2 = (opCode >> 16) & 0x7; + + Size = (opCode >> 8) & 0x3; + + bool single = Size != 3; + + RegisterSize = single ? RegisterSize.Int32 : RegisterSize.Int64; + + if (single) + { + Vm = ((opCode >> 5) & 0x1) | ((opCode << 1) & 0x1e); + Vd = ((opCode >> 22) & 0x1) | ((opCode >> 11) & 0x1e); + } + else + { + Vm = ((opCode >> 1) & 0x10) | ((opCode >> 0) & 0xf); + Vd = ((opCode >> 18) & 0x10) | ((opCode >> 12) & 0xf); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdSel.cs b/src/ARMeilleure/Decoders/OpCode32SimdSel.cs new file mode 100644 index 00000000..37fd714a --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdSel.cs @@ -0,0 +1,23 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdSel : OpCode32SimdRegS + { + public OpCode32SimdSelMode Cc { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdSel(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdSel(inst, address, opCode, true); + + public OpCode32SimdSel(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Cc = (OpCode32SimdSelMode)((opCode >> 20) & 3); + } + } + + enum OpCode32SimdSelMode : int + { + Eq = 0, + Vs, + Ge, + Gt + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdShImm.cs b/src/ARMeilleure/Decoders/OpCode32SimdShImm.cs new file mode 100644 index 00000000..55ddc395 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdShImm.cs @@ -0,0 +1,46 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdShImm : OpCode32Simd + { + public int Shift { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdShImm(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdShImm(inst, address, opCode, true); + + public OpCode32SimdShImm(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + int imm6 = (opCode >> 16) & 0x3f; + int limm6 = ((opCode >> 1) & 0x40) | imm6; + + if ((limm6 & 0x40) == 0b1000000) + { + Size = 3; + Shift = imm6; + } + else if ((limm6 & 0x60) == 0b0100000) + { + Size = 2; + Shift = imm6 - 32; + } + else if ((limm6 & 0x70) == 0b0010000) + { + Size = 1; + Shift = imm6 - 16; + } + else if ((limm6 & 0x78) == 0b0001000) + { + Size = 0; + Shift = imm6 - 8; + } + else + { + Instruction = InstDescriptor.Undefined; + } + + if (GetType() == typeof(OpCode32SimdShImm) && DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdShImmLong.cs b/src/ARMeilleure/Decoders/OpCode32SimdShImmLong.cs new file mode 100644 index 00000000..6b1b0ad1 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdShImmLong.cs @@ -0,0 +1,43 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdShImmLong : OpCode32Simd + { + public int Shift { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdShImmLong(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdShImmLong(inst, address, opCode, true); + + public OpCode32SimdShImmLong(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Q = false; + RegisterSize = RegisterSize.Simd64; + + int imm6 = (opCode >> 16) & 0x3f; + + if ((imm6 & 0x20) == 0b100000) + { + Size = 2; + Shift = imm6 - 32; + } + else if ((imm6 & 0x30) == 0b010000) + { + Size = 1; + Shift = imm6 - 16; + } + else if ((imm6 & 0x38) == 0b001000) + { + Size = 0; + Shift = imm6 - 8; + } + else + { + Instruction = InstDescriptor.Undefined; + } + + if (GetType() == typeof(OpCode32SimdShImmLong) && DecoderHelper.VectorArgumentsInvalid(true, Vd)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdShImmNarrow.cs b/src/ARMeilleure/Decoders/OpCode32SimdShImmNarrow.cs new file mode 100644 index 00000000..5351e65f --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdShImmNarrow.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdShImmNarrow : OpCode32SimdShImm + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdShImmNarrow(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdShImmNarrow(inst, address, opCode, true); + + public OpCode32SimdShImmNarrow(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) { } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdSpecial.cs b/src/ARMeilleure/Decoders/OpCode32SimdSpecial.cs new file mode 100644 index 00000000..61a9f387 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdSpecial.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdSpecial : OpCode32 + { + public int Rt { get; } + public int Sreg { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdSpecial(inst, address, opCode, false); + public static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdSpecial(inst, address, opCode, true); + + public OpCode32SimdSpecial(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode) + { + IsThumb = isThumb; + + Rt = (opCode >> 12) & 0xf; + Sreg = (opCode >> 16) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdSqrte.cs b/src/ARMeilleure/Decoders/OpCode32SimdSqrte.cs new file mode 100644 index 00000000..5b715535 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdSqrte.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdSqrte : OpCode32Simd + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdSqrte(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdSqrte(inst, address, opCode, true); + + public OpCode32SimdSqrte(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Size = (opCode >> 18) & 0x1; + F = ((opCode >> 8) & 0x1) != 0; + + if (DecoderHelper.VectorArgumentsInvalid(Q, Vd, Vm)) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32SimdTbl.cs b/src/ARMeilleure/Decoders/OpCode32SimdTbl.cs new file mode 100644 index 00000000..c4fb4b9c --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32SimdTbl.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32SimdTbl : OpCode32SimdReg + { + public int Length { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdTbl(inst, address, opCode, false); + public new static OpCode CreateT32(InstDescriptor inst, ulong address, int opCode) => new OpCode32SimdTbl(inst, address, opCode, true); + + public OpCode32SimdTbl(InstDescriptor inst, ulong address, int opCode, bool isThumb) : base(inst, address, opCode, isThumb) + { + Length = (opCode >> 8) & 3; + Size = 0; + Opc = Q ? 1 : 0; + Q = false; + RegisterSize = RegisterSize.Simd64; + + if (Vn + Length + 1 > 32) + { + Instruction = InstDescriptor.Undefined; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCode32System.cs b/src/ARMeilleure/Decoders/OpCode32System.cs new file mode 100644 index 00000000..89e93349 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCode32System.cs @@ -0,0 +1,28 @@ +namespace ARMeilleure.Decoders +{ + class OpCode32System : OpCode32 + { + public int Opc1 { get; } + public int CRn { get; } + public int Rt { get; } + public int Opc2 { get; } + public int CRm { get; } + public int MrrcOp { get; } + + public int Coproc { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCode32System(inst, address, opCode); + + public OpCode32System(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Opc1 = (opCode >> 21) & 0x7; + CRn = (opCode >> 16) & 0xf; + Rt = (opCode >> 12) & 0xf; + Opc2 = (opCode >> 5) & 0x7; + CRm = (opCode >> 0) & 0xf; + MrrcOp = (opCode >> 4) & 0xf; + + Coproc = (opCode >> 8) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeAdr.cs b/src/ARMeilleure/Decoders/OpCodeAdr.cs new file mode 100644 index 00000000..9655c766 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeAdr.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeAdr : OpCode + { + public int Rd { get; } + + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeAdr(inst, address, opCode); + + public OpCodeAdr(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = opCode & 0x1f; + + Immediate = DecoderHelper.DecodeImmS19_2(opCode); + Immediate |= ((long)opCode >> 29) & 3; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeAlu.cs b/src/ARMeilleure/Decoders/OpCodeAlu.cs new file mode 100644 index 00000000..4d7f03a7 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeAlu.cs @@ -0,0 +1,23 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeAlu : OpCode, IOpCodeAlu + { + public int Rd { get; protected set; } + public int Rn { get; } + + public DataOp DataOp { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeAlu(inst, address, opCode); + + public OpCodeAlu(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x1f; + Rn = (opCode >> 5) & 0x1f; + DataOp = (DataOp)((opCode >> 24) & 0x3); + + RegisterSize = (opCode >> 31) != 0 + ? RegisterSize.Int64 + : RegisterSize.Int32; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeAluBinary.cs b/src/ARMeilleure/Decoders/OpCodeAluBinary.cs new file mode 100644 index 00000000..e8b10656 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeAluBinary.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeAluBinary : OpCodeAlu + { + public int Rm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeAluBinary(inst, address, opCode); + + public OpCodeAluBinary(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 16) & 0x1f; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeAluImm.cs b/src/ARMeilleure/Decoders/OpCodeAluImm.cs new file mode 100644 index 00000000..91aa9553 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeAluImm.cs @@ -0,0 +1,40 @@ +using System; + +namespace ARMeilleure.Decoders +{ + class OpCodeAluImm : OpCodeAlu, IOpCodeAluImm + { + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeAluImm(inst, address, opCode); + + public OpCodeAluImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + if (DataOp == DataOp.Arithmetic) + { + Immediate = (opCode >> 10) & 0xfff; + + int shift = (opCode >> 22) & 3; + + Immediate <<= shift * 12; + } + else if (DataOp == DataOp.Logical) + { + var bm = DecoderHelper.DecodeBitMask(opCode, true); + + if (bm.IsUndefined) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Immediate = bm.WMask; + } + else + { + throw new ArgumentException(nameof(opCode)); + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeAluRs.cs b/src/ARMeilleure/Decoders/OpCodeAluRs.cs new file mode 100644 index 00000000..94983336 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeAluRs.cs @@ -0,0 +1,29 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeAluRs : OpCodeAlu, IOpCodeAluRs + { + public int Shift { get; } + public int Rm { get; } + + public ShiftType ShiftType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeAluRs(inst, address, opCode); + + public OpCodeAluRs(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int shift = (opCode >> 10) & 0x3f; + + if (shift >= GetBitsCount()) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Shift = shift; + + Rm = (opCode >> 16) & 0x1f; + ShiftType = (ShiftType)((opCode >> 22) & 0x3); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeAluRx.cs b/src/ARMeilleure/Decoders/OpCodeAluRx.cs new file mode 100644 index 00000000..d39da9e7 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeAluRx.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeAluRx : OpCodeAlu, IOpCodeAluRx + { + public int Shift { get; } + public int Rm { get; } + + public IntType IntType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeAluRx(inst, address, opCode); + + public OpCodeAluRx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Shift = (opCode >> 10) & 0x7; + IntType = (IntType)((opCode >> 13) & 0x7); + Rm = (opCode >> 16) & 0x1f; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeBImm.cs b/src/ARMeilleure/Decoders/OpCodeBImm.cs new file mode 100644 index 00000000..e302516e --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeBImm.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBImm : OpCode, IOpCodeBImm + { + public long Immediate { get; protected set; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeBImm(inst, address, opCode); + + public OpCodeBImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) { } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeBImmAl.cs b/src/ARMeilleure/Decoders/OpCodeBImmAl.cs new file mode 100644 index 00000000..47ae5f56 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeBImmAl.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBImmAl : OpCodeBImm + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeBImmAl(inst, address, opCode); + + public OpCodeBImmAl(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Immediate = (long)address + DecoderHelper.DecodeImm26_2(opCode); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeBImmCmp.cs b/src/ARMeilleure/Decoders/OpCodeBImmCmp.cs new file mode 100644 index 00000000..a5246569 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeBImmCmp.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBImmCmp : OpCodeBImm + { + public int Rt { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeBImmCmp(inst, address, opCode); + + public OpCodeBImmCmp(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = opCode & 0x1f; + + Immediate = (long)address + DecoderHelper.DecodeImmS19_2(opCode); + + RegisterSize = (opCode >> 31) != 0 + ? RegisterSize.Int64 + : RegisterSize.Int32; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeBImmCond.cs b/src/ARMeilleure/Decoders/OpCodeBImmCond.cs new file mode 100644 index 00000000..b57a7ea8 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeBImmCond.cs @@ -0,0 +1,25 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBImmCond : OpCodeBImm, IOpCodeCond + { + public Condition Cond { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeBImmCond(inst, address, opCode); + + public OpCodeBImmCond(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int o0 = (opCode >> 4) & 1; + + if (o0 != 0) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Cond = (Condition)(opCode & 0xf); + + Immediate = (long)address + DecoderHelper.DecodeImmS19_2(opCode); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeBImmTest.cs b/src/ARMeilleure/Decoders/OpCodeBImmTest.cs new file mode 100644 index 00000000..bad98405 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeBImmTest.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBImmTest : OpCodeBImm + { + public int Rt { get; } + public int Bit { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeBImmTest(inst, address, opCode); + + public OpCodeBImmTest(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = opCode & 0x1f; + + Immediate = (long)address + DecoderHelper.DecodeImmS14_2(opCode); + + Bit = (opCode >> 19) & 0x1f; + Bit |= (opCode >> 26) & 0x20; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeBReg.cs b/src/ARMeilleure/Decoders/OpCodeBReg.cs new file mode 100644 index 00000000..b5dcbfd8 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeBReg.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBReg : OpCode + { + public int Rn { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeBReg(inst, address, opCode); + + public OpCodeBReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int op4 = (opCode >> 0) & 0x1f; + int op2 = (opCode >> 16) & 0x1f; + + if (op2 != 0b11111 || op4 != 0b00000) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Rn = (opCode >> 5) & 0x1f; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeBfm.cs b/src/ARMeilleure/Decoders/OpCodeBfm.cs new file mode 100644 index 00000000..8e1c7836 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeBfm.cs @@ -0,0 +1,29 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeBfm : OpCodeAlu + { + public long WMask { get; } + public long TMask { get; } + public int Pos { get; } + public int Shift { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeBfm(inst, address, opCode); + + public OpCodeBfm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + var bm = DecoderHelper.DecodeBitMask(opCode, false); + + if (bm.IsUndefined) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + WMask = bm.WMask; + TMask = bm.TMask; + Pos = bm.Pos; + Shift = bm.Shift; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeCcmp.cs b/src/ARMeilleure/Decoders/OpCodeCcmp.cs new file mode 100644 index 00000000..aa47146f --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeCcmp.cs @@ -0,0 +1,32 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCodeCcmp : OpCodeAlu, IOpCodeCond + { + public int Nzcv { get; } + protected int RmImm; + + public Condition Cond { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeCcmp(inst, address, opCode); + + public OpCodeCcmp(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int o3 = (opCode >> 4) & 1; + + if (o3 != 0) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Nzcv = (opCode >> 0) & 0xf; + Cond = (Condition)((opCode >> 12) & 0xf); + RmImm = (opCode >> 16) & 0x1f; + + Rd = RegisterAlias.Zr; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeCcmpImm.cs b/src/ARMeilleure/Decoders/OpCodeCcmpImm.cs new file mode 100644 index 00000000..3548f2da --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeCcmpImm.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeCcmpImm : OpCodeCcmp, IOpCodeAluImm + { + public long Immediate => RmImm; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeCcmpImm(inst, address, opCode); + + public OpCodeCcmpImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) { } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeCcmpReg.cs b/src/ARMeilleure/Decoders/OpCodeCcmpReg.cs new file mode 100644 index 00000000..d5df3b10 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeCcmpReg.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeCcmpReg : OpCodeCcmp, IOpCodeAluRs + { + public int Rm => RmImm; + + public int Shift => 0; + + public ShiftType ShiftType => ShiftType.Lsl; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeCcmpReg(inst, address, opCode); + + public OpCodeCcmpReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) { } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeCsel.cs b/src/ARMeilleure/Decoders/OpCodeCsel.cs new file mode 100644 index 00000000..4b8dc7fd --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeCsel.cs @@ -0,0 +1,17 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeCsel : OpCodeAlu, IOpCodeCond + { + public int Rm { get; } + + public Condition Cond { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeCsel(inst, address, opCode); + + public OpCodeCsel(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 16) & 0x1f; + Cond = (Condition)((opCode >> 12) & 0xf); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeException.cs b/src/ARMeilleure/Decoders/OpCodeException.cs new file mode 100644 index 00000000..6b72138e --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeException.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeException : OpCode + { + public int Id { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeException(inst, address, opCode); + + public OpCodeException(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Id = (opCode >> 5) & 0xffff; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeMem.cs b/src/ARMeilleure/Decoders/OpCodeMem.cs new file mode 100644 index 00000000..0ba2bcd1 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMem.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMem : OpCode + { + public int Rt { get; protected set; } + public int Rn { get; protected set; } + public int Size { get; protected set; } + public bool Extend64 { get; protected set; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMem(inst, address, opCode); + + public OpCodeMem(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 0) & 0x1f; + Rn = (opCode >> 5) & 0x1f; + Size = (opCode >> 30) & 0x3; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeMemEx.cs b/src/ARMeilleure/Decoders/OpCodeMemEx.cs new file mode 100644 index 00000000..89902485 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMemEx.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMemEx : OpCodeMem + { + public int Rt2 { get; } + public int Rs { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMemEx(inst, address, opCode); + + public OpCodeMemEx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt2 = (opCode >> 10) & 0x1f; + Rs = (opCode >> 16) & 0x1f; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeMemImm.cs b/src/ARMeilleure/Decoders/OpCodeMemImm.cs new file mode 100644 index 00000000..d6ed2282 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMemImm.cs @@ -0,0 +1,53 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMemImm : OpCodeMem + { + public long Immediate { get; protected set; } + public bool WBack { get; protected set; } + public bool PostIdx { get; protected set; } + protected bool Unscaled { get; } + + private enum MemOp + { + Unscaled = 0, + PostIndexed = 1, + Unprivileged = 2, + PreIndexed = 3, + Unsigned + } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMemImm(inst, address, opCode); + + public OpCodeMemImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Extend64 = ((opCode >> 22) & 3) == 2; + WBack = ((opCode >> 24) & 1) == 0; + + // The type is not valid for the Unsigned Immediate 12-bits encoding, + // because the bits 11:10 are used for the larger Immediate offset. + MemOp type = WBack ? (MemOp)((opCode >> 10) & 3) : MemOp.Unsigned; + + PostIdx = type == MemOp.PostIndexed; + Unscaled = type == MemOp.Unscaled || + type == MemOp.Unprivileged; + + // Unscaled and Unprivileged doesn't write back, + // but they do use the 9-bits Signed Immediate. + if (Unscaled) + { + WBack = false; + } + + if (WBack || Unscaled) + { + // 9-bits Signed Immediate. + Immediate = (opCode << 11) >> 23; + } + else + { + // 12-bits Unsigned Immediate. + Immediate = ((opCode >> 10) & 0xfff) << Size; + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeMemLit.cs b/src/ARMeilleure/Decoders/OpCodeMemLit.cs new file mode 100644 index 00000000..986d6634 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMemLit.cs @@ -0,0 +1,28 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMemLit : OpCode, IOpCodeLit + { + public int Rt { get; } + public long Immediate { get; } + public int Size { get; } + public bool Signed { get; } + public bool Prefetch { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMemLit(inst, address, opCode); + + public OpCodeMemLit(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = opCode & 0x1f; + + Immediate = (long)address + DecoderHelper.DecodeImmS19_2(opCode); + + switch ((opCode >> 30) & 3) + { + case 0: Size = 2; Signed = false; Prefetch = false; break; + case 1: Size = 3; Signed = false; Prefetch = false; break; + case 2: Size = 2; Signed = true; Prefetch = false; break; + case 3: Size = 0; Signed = false; Prefetch = true; break; + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeMemPair.cs b/src/ARMeilleure/Decoders/OpCodeMemPair.cs new file mode 100644 index 00000000..21018033 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMemPair.cs @@ -0,0 +1,25 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMemPair : OpCodeMemImm + { + public int Rt2 { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMemPair(inst, address, opCode); + + public OpCodeMemPair(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt2 = (opCode >> 10) & 0x1f; + WBack = ((opCode >> 23) & 0x1) != 0; + PostIdx = ((opCode >> 23) & 0x3) == 1; + Extend64 = ((opCode >> 30) & 0x3) == 1; + Size = ((opCode >> 31) & 0x1) | 2; + + DecodeImm(opCode); + } + + protected void DecodeImm(int opCode) + { + Immediate = ((long)(opCode >> 15) << 57) >> (57 - Size); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeMemReg.cs b/src/ARMeilleure/Decoders/OpCodeMemReg.cs new file mode 100644 index 00000000..73d6c5d2 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMemReg.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMemReg : OpCodeMem + { + public bool Shift { get; } + public int Rm { get; } + + public IntType IntType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMemReg(inst, address, opCode); + + public OpCodeMemReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Shift = ((opCode >> 12) & 0x1) != 0; + IntType = (IntType)((opCode >> 13) & 0x7); + Rm = (opCode >> 16) & 0x1f; + Extend64 = ((opCode >> 22) & 0x3) == 2; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeMov.cs b/src/ARMeilleure/Decoders/OpCodeMov.cs new file mode 100644 index 00000000..50af88cb --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMov.cs @@ -0,0 +1,38 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMov : OpCode + { + public int Rd { get; } + + public long Immediate { get; } + + public int Bit { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMov(inst, address, opCode); + + public OpCodeMov(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int p1 = (opCode >> 22) & 1; + int sf = (opCode >> 31) & 1; + + if (sf == 0 && p1 != 0) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Rd = (opCode >> 0) & 0x1f; + Immediate = (opCode >> 5) & 0xffff; + Bit = (opCode >> 21) & 0x3; + + Bit <<= 4; + + Immediate <<= Bit; + + RegisterSize = (opCode >> 31) != 0 + ? RegisterSize.Int64 + : RegisterSize.Int32; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeMul.cs b/src/ARMeilleure/Decoders/OpCodeMul.cs new file mode 100644 index 00000000..31d140a6 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeMul.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeMul : OpCodeAlu + { + public int Rm { get; } + public int Ra { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeMul(inst, address, opCode); + + public OpCodeMul(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Ra = (opCode >> 10) & 0x1f; + Rm = (opCode >> 16) & 0x1f; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeSimd.cs b/src/ARMeilleure/Decoders/OpCodeSimd.cs new file mode 100644 index 00000000..85713690 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimd.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimd : OpCode, IOpCodeSimd + { + public int Rd { get; } + public int Rn { get; } + public int Opc { get; } + public int Size { get; protected set; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimd(inst, address, opCode); + + public OpCodeSimd(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x1f; + Rn = (opCode >> 5) & 0x1f; + Opc = (opCode >> 15) & 0x3; + Size = (opCode >> 22) & 0x3; + + RegisterSize = ((opCode >> 30) & 1) != 0 + ? RegisterSize.Simd128 + : RegisterSize.Simd64; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeSimdCvt.cs b/src/ARMeilleure/Decoders/OpCodeSimdCvt.cs new file mode 100644 index 00000000..05b32941 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdCvt.cs @@ -0,0 +1,21 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdCvt : OpCodeSimd + { + public int FBits { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdCvt(inst, address, opCode); + + public OpCodeSimdCvt(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int scale = (opCode >> 10) & 0x3f; + int sf = (opCode >> 31) & 0x1; + + FBits = 64 - scale; + + RegisterSize = sf != 0 + ? RegisterSize.Int64 + : RegisterSize.Int32; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeSimdExt.cs b/src/ARMeilleure/Decoders/OpCodeSimdExt.cs new file mode 100644 index 00000000..a0e264d9 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdExt.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdExt : OpCodeSimdReg + { + public int Imm4 { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdExt(inst, address, opCode); + + public OpCodeSimdExt(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Imm4 = (opCode >> 11) & 0xf; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeSimdFcond.cs b/src/ARMeilleure/Decoders/OpCodeSimdFcond.cs new file mode 100644 index 00000000..aa16e0c1 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdFcond.cs @@ -0,0 +1,17 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdFcond : OpCodeSimdReg, IOpCodeCond + { + public int Nzcv { get; } + + public Condition Cond { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdFcond(inst, address, opCode); + + public OpCodeSimdFcond(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Nzcv = (opCode >> 0) & 0xf; + Cond = (Condition)((opCode >> 12) & 0xf); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdFmov.cs b/src/ARMeilleure/Decoders/OpCodeSimdFmov.cs new file mode 100644 index 00000000..9f9062b8 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdFmov.cs @@ -0,0 +1,32 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdFmov : OpCode, IOpCodeSimd + { + public int Rd { get; } + public long Immediate { get; } + public int Size { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdFmov(inst, address, opCode); + + public OpCodeSimdFmov(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int type = (opCode >> 22) & 0x3; + + Size = type; + + long imm; + + Rd = (opCode >> 0) & 0x1f; + imm = (opCode >> 13) & 0xff; + + if (type == 0) + { + Immediate = (long)DecoderHelper.Imm8ToFP32Table[(int)imm]; + } + else /* if (type == 1) */ + { + Immediate = (long)DecoderHelper.Imm8ToFP64Table[(int)imm]; + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeSimdHelper.cs b/src/ARMeilleure/Decoders/OpCodeSimdHelper.cs new file mode 100644 index 00000000..02f74d03 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdHelper.cs @@ -0,0 +1,88 @@ +namespace ARMeilleure.Decoders +{ + public static class OpCodeSimdHelper + { + public static (long Immediate, int Size) GetSimdImmediateAndSize(int cMode, int op, long imm) + { + int modeLow = cMode & 1; + int modeHigh = cMode >> 1; + int size = 0; + + if (modeHigh == 0b111) + { + switch (op | (modeLow << 1)) + { + case 0: + // 64-bits Immediate. + // Transform abcd efgh into abcd efgh abcd efgh ... + size = 3; + imm = (long)((ulong)imm * 0x0101010101010101); + break; + + case 1: + // 64-bits Immediate. + // Transform abcd efgh into aaaa aaaa bbbb bbbb ... + size = 3; + imm = (imm & 0xf0) >> 4 | (imm & 0x0f) << 4; + imm = (imm & 0xcc) >> 2 | (imm & 0x33) << 2; + imm = (imm & 0xaa) >> 1 | (imm & 0x55) << 1; + + imm = (long)((ulong)imm * 0x8040201008040201); + imm = (long)((ulong)imm & 0x8080808080808080); + + imm |= imm >> 4; + imm |= imm >> 2; + imm |= imm >> 1; + break; + + case 2: + // 2 x 32-bits floating point Immediate. + size = 3; + imm = (long)DecoderHelper.Imm8ToFP32Table[(int)imm]; + imm |= imm << 32; + break; + + case 3: + // 64-bits floating point Immediate. + size = 3; + imm = (long)DecoderHelper.Imm8ToFP64Table[(int)imm]; + break; + } + } + else if ((modeHigh & 0b110) == 0b100) + { + // 16-bits shifted Immediate. + size = 1; imm <<= (modeHigh & 1) << 3; + } + else if ((modeHigh & 0b100) == 0b000) + { + // 32-bits shifted Immediate. + size = 2; imm <<= modeHigh << 3; + } + else if ((modeHigh & 0b111) == 0b110) + { + // 32-bits shifted Immediate (fill with ones). + size = 2; imm = ShlOnes(imm, 8 << modeLow); + } + else + { + // 8-bits without shift. + size = 0; + } + + return (imm, size); + } + + private static long ShlOnes(long value, int shift) + { + if (shift != 0) + { + return value << shift | (long)(ulong.MaxValue >> (64 - shift)); + } + else + { + return value; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdImm.cs b/src/ARMeilleure/Decoders/OpCodeSimdImm.cs new file mode 100644 index 00000000..eeca7709 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdImm.cs @@ -0,0 +1,107 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdImm : OpCode, IOpCodeSimd + { + public int Rd { get; } + public long Immediate { get; } + public int Size { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdImm(inst, address, opCode); + + public OpCodeSimdImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = opCode & 0x1f; + + int cMode = (opCode >> 12) & 0xf; + int op = (opCode >> 29) & 0x1; + + int modeLow = cMode & 1; + int modeHigh = cMode >> 1; + + long imm; + + imm = ((uint)opCode >> 5) & 0x1f; + imm |= ((uint)opCode >> 11) & 0xe0; + + if (modeHigh == 0b111) + { + switch (op | (modeLow << 1)) + { + case 0: + // 64-bits Immediate. + // Transform abcd efgh into abcd efgh abcd efgh ... + Size = 3; + imm = (long)((ulong)imm * 0x0101010101010101); + break; + + case 1: + // 64-bits Immediate. + // Transform abcd efgh into aaaa aaaa bbbb bbbb ... + Size = 3; + imm = (imm & 0xf0) >> 4 | (imm & 0x0f) << 4; + imm = (imm & 0xcc) >> 2 | (imm & 0x33) << 2; + imm = (imm & 0xaa) >> 1 | (imm & 0x55) << 1; + + imm = (long)((ulong)imm * 0x8040201008040201); + imm = (long)((ulong)imm & 0x8080808080808080); + + imm |= imm >> 4; + imm |= imm >> 2; + imm |= imm >> 1; + break; + + case 2: + // 2 x 32-bits floating point Immediate. + Size = 0; + imm = (long)DecoderHelper.Imm8ToFP32Table[(int)imm]; + imm |= imm << 32; + break; + + case 3: + // 64-bits floating point Immediate. + Size = 1; + imm = (long)DecoderHelper.Imm8ToFP64Table[(int)imm]; + break; + } + } + else if ((modeHigh & 0b110) == 0b100) + { + // 16-bits shifted Immediate. + Size = 1; imm <<= (modeHigh & 1) << 3; + } + else if ((modeHigh & 0b100) == 0b000) + { + // 32-bits shifted Immediate. + Size = 2; imm <<= modeHigh << 3; + } + else if ((modeHigh & 0b111) == 0b110) + { + // 32-bits shifted Immediate (fill with ones). + Size = 2; imm = ShlOnes(imm, 8 << modeLow); + } + else + { + // 8-bits without shift. + Size = 0; + } + + Immediate = imm; + + RegisterSize = ((opCode >> 30) & 1) != 0 + ? RegisterSize.Simd128 + : RegisterSize.Simd64; + } + + private static long ShlOnes(long value, int shift) + { + if (shift != 0) + { + return value << shift | (long)(ulong.MaxValue >> (64 - shift)); + } + else + { + return value; + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeSimdIns.cs b/src/ARMeilleure/Decoders/OpCodeSimdIns.cs new file mode 100644 index 00000000..f6f9249d --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdIns.cs @@ -0,0 +1,36 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdIns : OpCodeSimd + { + public int SrcIndex { get; } + public int DstIndex { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdIns(inst, address, opCode); + + public OpCodeSimdIns(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int imm4 = (opCode >> 11) & 0xf; + int imm5 = (opCode >> 16) & 0x1f; + + if (imm5 == 0b10000) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Size = imm5 & -imm5; + + switch (Size) + { + case 1: Size = 0; break; + case 2: Size = 1; break; + case 4: Size = 2; break; + case 8: Size = 3; break; + } + + SrcIndex = imm4 >> Size; + DstIndex = imm5 >> (Size + 1); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeSimdMemImm.cs b/src/ARMeilleure/Decoders/OpCodeSimdMemImm.cs new file mode 100644 index 00000000..c11594cb --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdMemImm.cs @@ -0,0 +1,28 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemImm : OpCodeMemImm, IOpCodeSimd + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdMemImm(inst, address, opCode); + + public OpCodeSimdMemImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Size |= (opCode >> 21) & 4; + + if (Size > 4) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + // Base class already shifts the immediate, we only + // need to shift it if size (scale) is 4, since this value is only set here. + if (!WBack && !Unscaled && Size == 4) + { + Immediate <<= 4; + } + + Extend64 = false; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeSimdMemLit.cs b/src/ARMeilleure/Decoders/OpCodeSimdMemLit.cs new file mode 100644 index 00000000..8e212966 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdMemLit.cs @@ -0,0 +1,31 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemLit : OpCode, IOpCodeSimd, IOpCodeLit + { + public int Rt { get; } + public long Immediate { get; } + public int Size { get; } + public bool Signed => false; + public bool Prefetch => false; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdMemLit(inst, address, opCode); + + public OpCodeSimdMemLit(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int opc = (opCode >> 30) & 3; + + if (opc == 3) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Rt = opCode & 0x1f; + + Immediate = (long)address + DecoderHelper.DecodeImmS19_2(opCode); + + Size = opc + 2; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeSimdMemMs.cs b/src/ARMeilleure/Decoders/OpCodeSimdMemMs.cs new file mode 100644 index 00000000..8922c18f --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdMemMs.cs @@ -0,0 +1,48 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemMs : OpCodeMemReg, IOpCodeSimd + { + public int Reps { get; } + public int SElems { get; } + public int Elems { get; } + public bool WBack { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdMemMs(inst, address, opCode); + + public OpCodeSimdMemMs(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + switch ((opCode >> 12) & 0xf) + { + case 0b0000: Reps = 1; SElems = 4; break; + case 0b0010: Reps = 4; SElems = 1; break; + case 0b0100: Reps = 1; SElems = 3; break; + case 0b0110: Reps = 3; SElems = 1; break; + case 0b0111: Reps = 1; SElems = 1; break; + case 0b1000: Reps = 1; SElems = 2; break; + case 0b1010: Reps = 2; SElems = 1; break; + + default: Instruction = InstDescriptor.Undefined; return; + } + + Size = (opCode >> 10) & 3; + WBack = ((opCode >> 23) & 1) != 0; + + bool q = ((opCode >> 30) & 1) != 0; + + if (!q && Size == 3 && SElems != 1) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Extend64 = false; + + RegisterSize = q + ? RegisterSize.Simd128 + : RegisterSize.Simd64; + + Elems = (GetBitsCount() >> 3) >> Size; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeSimdMemPair.cs b/src/ARMeilleure/Decoders/OpCodeSimdMemPair.cs new file mode 100644 index 00000000..1ab95367 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdMemPair.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemPair : OpCodeMemPair, IOpCodeSimd + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdMemPair(inst, address, opCode); + + public OpCodeSimdMemPair(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Size = ((opCode >> 30) & 3) + 2; + + Extend64 = false; + + DecodeImm(opCode); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeSimdMemReg.cs b/src/ARMeilleure/Decoders/OpCodeSimdMemReg.cs new file mode 100644 index 00000000..9ea6dda3 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdMemReg.cs @@ -0,0 +1,21 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemReg : OpCodeMemReg, IOpCodeSimd + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdMemReg(inst, address, opCode); + + public OpCodeSimdMemReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Size |= (opCode >> 21) & 4; + + if (Size > 4) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + Extend64 = false; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeSimdMemSs.cs b/src/ARMeilleure/Decoders/OpCodeSimdMemSs.cs new file mode 100644 index 00000000..44abdd38 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdMemSs.cs @@ -0,0 +1,97 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdMemSs : OpCodeMemReg, IOpCodeSimd + { + public int SElems { get; } + public int Index { get; } + public bool Replicate { get; } + public bool WBack { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdMemSs(inst, address, opCode); + + public OpCodeSimdMemSs(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int size = (opCode >> 10) & 3; + int s = (opCode >> 12) & 1; + int sElems = (opCode >> 12) & 2; + int scale = (opCode >> 14) & 3; + int l = (opCode >> 22) & 1; + int q = (opCode >> 30) & 1; + + sElems |= (opCode >> 21) & 1; + + sElems++; + + int index = (q << 3) | (s << 2) | size; + + switch (scale) + { + case 1: + { + if ((size & 1) != 0) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + index >>= 1; + + break; + } + + case 2: + { + if ((size & 2) != 0 || + ((size & 1) != 0 && s != 0)) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + if ((size & 1) != 0) + { + index >>= 3; + + scale = 3; + } + else + { + index >>= 2; + } + + break; + } + + case 3: + { + if (l == 0 || s != 0) + { + Instruction = InstDescriptor.Undefined; + + return; + } + + scale = size; + + Replicate = true; + + break; + } + } + + Index = index; + SElems = sElems; + Size = scale; + + Extend64 = false; + + WBack = ((opCode >> 23) & 1) != 0; + + RegisterSize = q != 0 + ? RegisterSize.Simd128 + : RegisterSize.Simd64; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeSimdReg.cs b/src/ARMeilleure/Decoders/OpCodeSimdReg.cs new file mode 100644 index 00000000..ac4f71da --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdReg.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdReg : OpCodeSimd + { + public bool Bit3 { get; } + public int Ra { get; } + public int Rm { get; protected set; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdReg(inst, address, opCode); + + public OpCodeSimdReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Bit3 = ((opCode >> 3) & 0x1) != 0; + Ra = (opCode >> 10) & 0x1f; + Rm = (opCode >> 16) & 0x1f; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeSimdRegElem.cs b/src/ARMeilleure/Decoders/OpCodeSimdRegElem.cs new file mode 100644 index 00000000..92368dee --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdRegElem.cs @@ -0,0 +1,31 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdRegElem : OpCodeSimdReg + { + public int Index { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdRegElem(inst, address, opCode); + + public OpCodeSimdRegElem(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + switch (Size) + { + case 1: + Index = (opCode >> 20) & 3 | + (opCode >> 9) & 4; + + Rm &= 0xf; + + break; + + case 2: + Index = (opCode >> 21) & 1 | + (opCode >> 10) & 2; + + break; + + default: Instruction = InstDescriptor.Undefined; break; + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeSimdRegElemF.cs b/src/ARMeilleure/Decoders/OpCodeSimdRegElemF.cs new file mode 100644 index 00000000..d46dd57e --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdRegElemF.cs @@ -0,0 +1,33 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdRegElemF : OpCodeSimdReg + { + public int Index { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdRegElemF(inst, address, opCode); + + public OpCodeSimdRegElemF(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + switch ((opCode >> 21) & 3) // sz:L + { + case 0: // H:0 + Index = (opCode >> 10) & 2; // 0, 2 + + break; + + case 1: // H:1 + Index = (opCode >> 10) & 2; + Index++; // 1, 3 + + break; + + case 2: // H + Index = (opCode >> 11) & 1; // 0, 1 + + break; + + default: Instruction = InstDescriptor.Undefined; break; + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdShImm.cs b/src/ARMeilleure/Decoders/OpCodeSimdShImm.cs new file mode 100644 index 00000000..7064f1d2 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdShImm.cs @@ -0,0 +1,18 @@ +using ARMeilleure.Common; + +namespace ARMeilleure.Decoders +{ + class OpCodeSimdShImm : OpCodeSimd + { + public int Imm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdShImm(inst, address, opCode); + + public OpCodeSimdShImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Imm = (opCode >> 16) & 0x7f; + + Size = BitUtils.HighestBitSetNibble(Imm >> 3); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeSimdTbl.cs b/src/ARMeilleure/Decoders/OpCodeSimdTbl.cs new file mode 100644 index 00000000..9c631e48 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSimdTbl.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSimdTbl : OpCodeSimdReg + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSimdTbl(inst, address, opCode); + + public OpCodeSimdTbl(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Size = ((opCode >> 13) & 3) + 1; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeSystem.cs b/src/ARMeilleure/Decoders/OpCodeSystem.cs new file mode 100644 index 00000000..4d79421a --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeSystem.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeSystem : OpCode + { + public int Rt { get; } + public int Op2 { get; } + public int CRm { get; } + public int CRn { get; } + public int Op1 { get; } + public int Op0 { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeSystem(inst, address, opCode); + + public OpCodeSystem(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 0) & 0x1f; + Op2 = (opCode >> 5) & 0x7; + CRm = (opCode >> 8) & 0xf; + CRn = (opCode >> 12) & 0xf; + Op1 = (opCode >> 16) & 0x7; + Op0 = ((opCode >> 19) & 0x1) | 2; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeT16.cs b/src/ARMeilleure/Decoders/OpCodeT16.cs new file mode 100644 index 00000000..9c3d6b00 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16 : OpCode32 + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16(inst, address, opCode); + + public OpCodeT16(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Cond = Condition.Al; + + IsThumb = true; + OpCodeSizeInBytes = 2; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeT16AddSubImm3.cs b/src/ARMeilleure/Decoders/OpCodeT16AddSubImm3.cs new file mode 100644 index 00000000..95f18054 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AddSubImm3.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16AddSubImm3: OpCodeT16, IOpCode32AluImm + { + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags => null; + + public int Immediate { get; } + + public bool IsRotated { get; } + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AddSubImm3(inst, address, opCode); + + public OpCodeT16AddSubImm3(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x7; + Rn = (opCode >> 3) & 0x7; + Immediate = (opCode >> 6) & 0x7; + IsRotated = false; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AddSubReg.cs b/src/ARMeilleure/Decoders/OpCodeT16AddSubReg.cs new file mode 100644 index 00000000..2a407b2d --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AddSubReg.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16AddSubReg : OpCodeT16, IOpCode32AluReg + { + public int Rm { get; } + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags => null; + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AddSubReg(inst, address, opCode); + + public OpCodeT16AddSubReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x7; + Rn = (opCode >> 3) & 0x7; + Rm = (opCode >> 6) & 0x7; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AddSubSp.cs b/src/ARMeilleure/Decoders/OpCodeT16AddSubSp.cs new file mode 100644 index 00000000..b66fe0cd --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AddSubSp.cs @@ -0,0 +1,23 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16AddSubSp : OpCodeT16, IOpCode32AluImm + { + public int Rd => RegisterAlias.Aarch32Sp; + public int Rn => RegisterAlias.Aarch32Sp; + + public bool? SetFlags => false; + + public int Immediate { get; } + + public bool IsRotated => false; + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AddSubSp(inst, address, opCode); + + public OpCodeT16AddSubSp(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Immediate = ((opCode >> 0) & 0x7f) << 2; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16Adr.cs b/src/ARMeilleure/Decoders/OpCodeT16Adr.cs new file mode 100644 index 00000000..03abd499 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16Adr.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16Adr : OpCodeT16, IOpCode32Adr + { + public int Rd { get; } + + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16Adr(inst, address, opCode); + + public OpCodeT16Adr(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 8) & 7; + + int imm = (opCode & 0xff) << 2; + Immediate = (int)(GetPc() & 0xfffffffc) + imm; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AluImm8.cs b/src/ARMeilleure/Decoders/OpCodeT16AluImm8.cs new file mode 100644 index 00000000..673a4604 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AluImm8.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16AluImm8 : OpCodeT16, IOpCode32AluImm + { + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags => null; + + public int Immediate { get; } + + public bool IsRotated { get; } + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AluImm8(inst, address, opCode); + + public OpCodeT16AluImm8(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 8) & 0x7; + Rn = (opCode >> 8) & 0x7; + Immediate = (opCode >> 0) & 0xff; + IsRotated = false; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AluImmZero.cs b/src/ARMeilleure/Decoders/OpCodeT16AluImmZero.cs new file mode 100644 index 00000000..b23f8fe0 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AluImmZero.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16AluImmZero : OpCodeT16, IOpCode32AluImm + { + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags => null; + + public int Immediate { get; } + + public bool IsRotated { get; } + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AluImmZero(inst, address, opCode); + + public OpCodeT16AluImmZero(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x7; + Rn = (opCode >> 3) & 0x7; + Immediate = 0; + IsRotated = false; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AluRegHigh.cs b/src/ARMeilleure/Decoders/OpCodeT16AluRegHigh.cs new file mode 100644 index 00000000..6d5ac8fd --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AluRegHigh.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16AluRegHigh : OpCodeT16, IOpCode32AluReg + { + public int Rm { get; } + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags => false; + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AluRegHigh(inst, address, opCode); + + public OpCodeT16AluRegHigh(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = ((opCode >> 0) & 0x7) | ((opCode >> 4) & 0x8); + Rn = ((opCode >> 0) & 0x7) | ((opCode >> 4) & 0x8); + Rm = (opCode >> 3) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AluRegLow.cs b/src/ARMeilleure/Decoders/OpCodeT16AluRegLow.cs new file mode 100644 index 00000000..b37b4f66 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AluRegLow.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16AluRegLow : OpCodeT16, IOpCode32AluReg + { + public int Rm { get; } + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags => null; + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AluRegLow(inst, address, opCode); + + public OpCodeT16AluRegLow(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x7; + Rn = (opCode >> 0) & 0x7; + Rm = (opCode >> 3) & 0x7; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16AluUx.cs b/src/ARMeilleure/Decoders/OpCodeT16AluUx.cs new file mode 100644 index 00000000..11d3a8fe --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16AluUx.cs @@ -0,0 +1,22 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16AluUx : OpCodeT16, IOpCode32AluUx + { + public int Rm { get; } + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags => false; + + public int RotateBits => 0; + public bool Add => false; + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16AluUx(inst, address, opCode); + + public OpCodeT16AluUx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x7; + Rm = (opCode >> 3) & 0x7; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16BImm11.cs b/src/ARMeilleure/Decoders/OpCodeT16BImm11.cs new file mode 100644 index 00000000..f230b20e --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16BImm11.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16BImm11 : OpCodeT16, IOpCode32BImm + { + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16BImm11(inst, address, opCode); + + public OpCodeT16BImm11(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int imm = (opCode << 21) >> 20; + Immediate = GetPc() + imm; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16BImm8.cs b/src/ARMeilleure/Decoders/OpCodeT16BImm8.cs new file mode 100644 index 00000000..5f684298 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16BImm8.cs @@ -0,0 +1,17 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16BImm8 : OpCodeT16, IOpCode32BImm + { + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16BImm8(inst, address, opCode); + + public OpCodeT16BImm8(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Cond = (Condition)((opCode >> 8) & 0xf); + + int imm = (opCode << 24) >> 23; + Immediate = GetPc() + imm; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16BImmCmp.cs b/src/ARMeilleure/Decoders/OpCodeT16BImmCmp.cs new file mode 100644 index 00000000..68ebac75 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16BImmCmp.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16BImmCmp : OpCodeT16, IOpCode32BImm + { + public int Rn { get; } + + public long Immediate { get; } + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16BImmCmp(inst, address, opCode); + + public OpCodeT16BImmCmp(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rn = (opCode >> 0) & 0x7; + + int imm = ((opCode >> 2) & 0x3e) | ((opCode >> 3) & 0x40); + Immediate = (int)GetPc() + imm; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16BReg.cs b/src/ARMeilleure/Decoders/OpCodeT16BReg.cs new file mode 100644 index 00000000..3122cd07 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16BReg.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16BReg : OpCodeT16, IOpCode32BReg + { + public int Rm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16BReg(inst, address, opCode); + + public OpCodeT16BReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 3) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16Exception.cs b/src/ARMeilleure/Decoders/OpCodeT16Exception.cs new file mode 100644 index 00000000..bb005083 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16Exception.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16Exception : OpCodeT16, IOpCode32Exception + { + public int Id { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16Exception(inst, address, opCode); + + public OpCodeT16Exception(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Id = opCode & 0xFF; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16IfThen.cs b/src/ARMeilleure/Decoders/OpCodeT16IfThen.cs new file mode 100644 index 00000000..8c3de689 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16IfThen.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16IfThen : OpCodeT16 + { + public Condition[] IfThenBlockConds { get; } + + public int IfThenBlockSize { get { return IfThenBlockConds.Length; } } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16IfThen(inst, address, opCode); + + public OpCodeT16IfThen(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + List conds = new(); + + int cond = (opCode >> 4) & 0xf; + int mask = opCode & 0xf; + + conds.Add((Condition)cond); + + while ((mask & 7) != 0) + { + int newLsb = (mask >> 3) & 1; + cond = (cond & 0xe) | newLsb; + mask <<= 1; + conds.Add((Condition)cond); + } + + IfThenBlockConds = conds.ToArray(); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16MemImm5.cs b/src/ARMeilleure/Decoders/OpCodeT16MemImm5.cs new file mode 100644 index 00000000..20ef31e2 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16MemImm5.cs @@ -0,0 +1,58 @@ +using ARMeilleure.Instructions; +using System; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16MemImm5 : OpCodeT16, IOpCode32Mem + { + public int Rt { get; } + public int Rn { get; } + + public bool WBack => false; + public bool IsLoad { get; } + public bool Index => true; + public bool Add => true; + + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemImm5(inst, address, opCode); + + public OpCodeT16MemImm5(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 0) & 7; + Rn = (opCode >> 3) & 7; + + switch (inst.Name) + { + case InstName.Ldr: + case InstName.Ldrb: + case InstName.Ldrh: + IsLoad = true; + break; + case InstName.Str: + case InstName.Strb: + case InstName.Strh: + IsLoad = false; + break; + } + + switch (inst.Name) + { + case InstName.Str: + case InstName.Ldr: + Immediate = ((opCode >> 6) & 0x1f) << 2; + break; + case InstName.Strb: + case InstName.Ldrb: + Immediate = ((opCode >> 6) & 0x1f); + break; + case InstName.Strh: + case InstName.Ldrh: + Immediate = ((opCode >> 6) & 0x1f) << 1; + break; + default: + throw new InvalidOperationException(); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16MemLit.cs b/src/ARMeilleure/Decoders/OpCodeT16MemLit.cs new file mode 100644 index 00000000..f8c16e29 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16MemLit.cs @@ -0,0 +1,26 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16MemLit : OpCodeT16, IOpCode32Mem + { + public int Rt { get; } + public int Rn => RegisterAlias.Aarch32Pc; + + public bool WBack => false; + public bool IsLoad => true; + public bool Index => true; + public bool Add => true; + + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemLit(inst, address, opCode); + + public OpCodeT16MemLit(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 8) & 7; + + Immediate = (opCode & 0xff) << 2; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16MemMult.cs b/src/ARMeilleure/Decoders/OpCodeT16MemMult.cs new file mode 100644 index 00000000..f4185cfc --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16MemMult.cs @@ -0,0 +1,34 @@ +using ARMeilleure.Instructions; +using System; +using System.Numerics; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16MemMult : OpCodeT16, IOpCode32MemMult + { + public int Rn { get; } + public int RegisterMask { get; } + public int PostOffset { get; } + public bool IsLoad { get; } + public int Offset { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemMult(inst, address, opCode); + + public OpCodeT16MemMult(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + RegisterMask = opCode & 0xff; + Rn = (opCode >> 8) & 7; + + int regCount = BitOperations.PopCount((uint)RegisterMask); + + Offset = 0; + PostOffset = 4 * regCount; + IsLoad = inst.Name switch + { + InstName.Ldm => true, + InstName.Stm => false, + _ => throw new InvalidOperationException() + }; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16MemReg.cs b/src/ARMeilleure/Decoders/OpCodeT16MemReg.cs new file mode 100644 index 00000000..71100112 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16MemReg.cs @@ -0,0 +1,27 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16MemReg : OpCodeT16, IOpCode32MemReg + { + public int Rm { get; } + public int Rt { get; } + public int Rn { get; } + + public bool WBack => false; + public bool IsLoad { get; } + public bool Index => true; + public bool Add => true; + + public int Immediate => throw new System.InvalidOperationException(); + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemReg(inst, address, opCode); + + public OpCodeT16MemReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 0) & 7; + Rn = (opCode >> 3) & 7; + Rm = (opCode >> 6) & 7; + + IsLoad = ((opCode >> 9) & 7) >= 3; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16MemSp.cs b/src/ARMeilleure/Decoders/OpCodeT16MemSp.cs new file mode 100644 index 00000000..a038b915 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16MemSp.cs @@ -0,0 +1,28 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16MemSp : OpCodeT16, IOpCode32Mem + { + public int Rt { get; } + public int Rn => RegisterAlias.Aarch32Sp; + + public bool WBack => false; + public bool IsLoad { get; } + public bool Index => true; + public bool Add => true; + + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemSp(inst, address, opCode); + + public OpCodeT16MemSp(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 8) & 7; + + IsLoad = ((opCode >> 11) & 1) != 0; + + Immediate = ((opCode >> 0) & 0xff) << 2; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16MemStack.cs b/src/ARMeilleure/Decoders/OpCodeT16MemStack.cs new file mode 100644 index 00000000..9d7b0d20 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16MemStack.cs @@ -0,0 +1,42 @@ +using ARMeilleure.Instructions; +using ARMeilleure.State; +using System; +using System.Numerics; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16MemStack : OpCodeT16, IOpCode32MemMult + { + public int Rn => RegisterAlias.Aarch32Sp; + public int RegisterMask { get; } + public int PostOffset { get; } + public bool IsLoad { get; } + public int Offset { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16MemStack(inst, address, opCode); + + public OpCodeT16MemStack(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int extra = (opCode >> 8) & 1; + int regCount = BitOperations.PopCount((uint)opCode & 0x1ff); + + switch (inst.Name) + { + case InstName.Push: + RegisterMask = (opCode & 0xff) | (extra << 14); + IsLoad = false; + Offset = -4 * regCount; + PostOffset = -4 * regCount; + break; + case InstName.Pop: + RegisterMask = (opCode & 0xff) | (extra << 15); + IsLoad = true; + Offset = 0; + PostOffset = 4 * regCount; + break; + default: + throw new InvalidOperationException(); + } + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16ShiftImm.cs b/src/ARMeilleure/Decoders/OpCodeT16ShiftImm.cs new file mode 100644 index 00000000..a540026e --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16ShiftImm.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16ShiftImm : OpCodeT16, IOpCode32AluRsImm + { + public int Rd { get; } + public int Rn { get; } + public int Rm { get; } + + public int Immediate { get; } + public ShiftType ShiftType { get; } + + public bool? SetFlags => null; + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16ShiftImm(inst, address, opCode); + + public OpCodeT16ShiftImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0x7; + Rm = (opCode >> 3) & 0x7; + Immediate = (opCode >> 6) & 0x1F; + ShiftType = (ShiftType)((opCode >> 11) & 3); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16ShiftReg.cs b/src/ARMeilleure/Decoders/OpCodeT16ShiftReg.cs new file mode 100644 index 00000000..9f898281 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16ShiftReg.cs @@ -0,0 +1,27 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT16ShiftReg : OpCodeT16, IOpCode32AluRsReg + { + public int Rm { get; } + public int Rs { get; } + public int Rd { get; } + + public int Rn { get; } + + public ShiftType ShiftType { get; } + + public bool? SetFlags => null; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16ShiftReg(inst, address, opCode); + + public OpCodeT16ShiftReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 7; + Rm = (opCode >> 0) & 7; + Rn = (opCode >> 3) & 7; + Rs = (opCode >> 3) & 7; + + ShiftType = (ShiftType)(((opCode >> 6) & 1) | ((opCode >> 7) & 2)); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT16SpRel.cs b/src/ARMeilleure/Decoders/OpCodeT16SpRel.cs new file mode 100644 index 00000000..d737f5bd --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT16SpRel.cs @@ -0,0 +1,24 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCodeT16SpRel : OpCodeT16, IOpCode32AluImm + { + public int Rd { get; } + public int Rn => RegisterAlias.Aarch32Sp; + + public bool? SetFlags => false; + + public int Immediate { get; } + + public bool IsRotated => false; + + public static new OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT16SpRel(inst, address, opCode); + + public OpCodeT16SpRel(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 8) & 0x7; + Immediate = ((opCode >> 0) & 0xff) << 2; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32.cs b/src/ARMeilleure/Decoders/OpCodeT32.cs new file mode 100644 index 00000000..cf43d429 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32 : OpCode32 + { + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32(inst, address, opCode); + + public OpCodeT32(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Cond = Condition.Al; + + IsThumb = true; + OpCodeSizeInBytes = 4; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeT32Alu.cs b/src/ARMeilleure/Decoders/OpCodeT32Alu.cs new file mode 100644 index 00000000..a81b3b3d --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32Alu.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32Alu : OpCodeT32, IOpCode32Alu + { + public int Rd { get; } + public int Rn { get; } + + public bool? SetFlags { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32Alu(inst, address, opCode); + + public OpCodeT32Alu(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 8) & 0xf; + Rn = (opCode >> 16) & 0xf; + + SetFlags = ((opCode >> 20) & 1) != 0; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluBf.cs b/src/ARMeilleure/Decoders/OpCodeT32AluBf.cs new file mode 100644 index 00000000..57ad422f --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluBf.cs @@ -0,0 +1,22 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluBf : OpCodeT32, IOpCode32AluBf + { + public int Rd { get; } + public int Rn { get; } + + public int Msb { get; } + public int Lsb { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluBf(inst, address, opCode); + + public OpCodeT32AluBf(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 8) & 0xf; + Rn = (opCode >> 16) & 0xf; + + Msb = (opCode >> 0) & 0x1f; + Lsb = ((opCode >> 6) & 0x3) | ((opCode >> 10) & 0x1c); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluImm.cs b/src/ARMeilleure/Decoders/OpCodeT32AluImm.cs new file mode 100644 index 00000000..0895c29b --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluImm.cs @@ -0,0 +1,38 @@ +using ARMeilleure.Common; +using System.Runtime.Intrinsics; + +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluImm : OpCodeT32Alu, IOpCode32AluImm + { + public int Immediate { get; } + + public bool IsRotated { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluImm(inst, address, opCode); + + private static readonly Vector128 _factor = Vector128.Create(1, 0x00010001, 0x01000100, 0x01010101); + + public OpCodeT32AluImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + int imm8 = (opCode >> 0) & 0xff; + int imm3 = (opCode >> 12) & 7; + int imm1 = (opCode >> 26) & 1; + + int imm12 = imm8 | (imm3 << 8) | (imm1 << 11); + + if ((imm12 >> 10) == 0) + { + Immediate = imm8 * _factor.GetElement((imm12 >> 8) & 3); + IsRotated = false; + } + else + { + int shift = imm12 >> 7; + + Immediate = BitUtils.RotateRight(0x80 | (imm12 & 0x7f), shift, 32); + IsRotated = shift != 0; + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluImm12.cs b/src/ARMeilleure/Decoders/OpCodeT32AluImm12.cs new file mode 100644 index 00000000..31de63dd --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluImm12.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluImm12 : OpCodeT32Alu, IOpCode32AluImm + { + public int Immediate { get; } + + public bool IsRotated => false; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluImm12(inst, address, opCode); + + public OpCodeT32AluImm12(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Immediate = (opCode & 0xff) | ((opCode >> 4) & 0x700) | ((opCode >> 15) & 0x800); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluMla.cs b/src/ARMeilleure/Decoders/OpCodeT32AluMla.cs new file mode 100644 index 00000000..6cb604da --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluMla.cs @@ -0,0 +1,29 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluMla : OpCodeT32, IOpCode32AluMla + { + public int Rn { get; } + public int Rm { get; } + public int Ra { get; } + public int Rd { get; } + + public bool NHigh { get; } + public bool MHigh { get; } + public bool R { get; } + public bool? SetFlags => false; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluMla(inst, address, opCode); + + public OpCodeT32AluMla(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + Rd = (opCode >> 8) & 0xf; + Ra = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + R = (opCode & (1 << 4)) != 0; + + MHigh = ((opCode >> 4) & 0x1) == 1; + NHigh = ((opCode >> 5) & 0x1) == 1; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluReg.cs b/src/ARMeilleure/Decoders/OpCodeT32AluReg.cs new file mode 100644 index 00000000..a487f55a --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluReg.cs @@ -0,0 +1,14 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluReg : OpCodeT32Alu, IOpCode32AluReg + { + public int Rm { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluReg(inst, address, opCode); + + public OpCodeT32AluReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluRsImm.cs b/src/ARMeilleure/Decoders/OpCodeT32AluRsImm.cs new file mode 100644 index 00000000..1c9ba7a2 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluRsImm.cs @@ -0,0 +1,20 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluRsImm : OpCodeT32Alu, IOpCode32AluRsImm + { + public int Rm { get; } + public int Immediate { get; } + + public ShiftType ShiftType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluRsImm(inst, address, opCode); + + public OpCodeT32AluRsImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + Immediate = ((opCode >> 6) & 3) | ((opCode >> 10) & 0x1c); + + ShiftType = (ShiftType)((opCode >> 4) & 3); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluUmull.cs b/src/ARMeilleure/Decoders/OpCodeT32AluUmull.cs new file mode 100644 index 00000000..a1b2e612 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluUmull.cs @@ -0,0 +1,28 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluUmull : OpCodeT32, IOpCode32AluUmull + { + public int RdLo { get; } + public int RdHi { get; } + public int Rn { get; } + public int Rm { get; } + + public bool NHigh { get; } + public bool MHigh { get; } + + public bool? SetFlags => false; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluUmull(inst, address, opCode); + + public OpCodeT32AluUmull(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + RdHi = (opCode >> 8) & 0xf; + RdLo = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + + MHigh = ((opCode >> 4) & 0x1) == 1; + NHigh = ((opCode >> 5) & 0x1) == 1; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32AluUx.cs b/src/ARMeilleure/Decoders/OpCodeT32AluUx.cs new file mode 100644 index 00000000..861dc904 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32AluUx.cs @@ -0,0 +1,18 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Decoders +{ + class OpCodeT32AluUx : OpCodeT32AluReg, IOpCode32AluUx + { + public int Rotate { get; } + public int RotateBits => Rotate * 8; + public bool Add => Rn != RegisterAlias.Aarch32Pc; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32AluUx(inst, address, opCode); + + public OpCodeT32AluUx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rotate = (opCode >> 4) & 0x3; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32BImm20.cs b/src/ARMeilleure/Decoders/OpCodeT32BImm20.cs new file mode 100644 index 00000000..b6da8abd --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32BImm20.cs @@ -0,0 +1,27 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32BImm20 : OpCodeT32, IOpCode32BImm + { + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32BImm20(inst, address, opCode); + + public OpCodeT32BImm20(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + uint pc = GetPc(); + + int imm11 = (opCode >> 0) & 0x7ff; + int j2 = (opCode >> 11) & 1; + int j1 = (opCode >> 13) & 1; + int imm6 = (opCode >> 16) & 0x3f; + int s = (opCode >> 26) & 1; + + int imm32 = imm11 | (imm6 << 11) | (j1 << 17) | (j2 << 18) | (s << 19); + imm32 = (imm32 << 13) >> 12; + + Immediate = pc + imm32; + + Cond = (Condition)((opCode >> 22) & 0xf); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeT32BImm24.cs b/src/ARMeilleure/Decoders/OpCodeT32BImm24.cs new file mode 100644 index 00000000..774ec3a6 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32BImm24.cs @@ -0,0 +1,35 @@ +using ARMeilleure.Instructions; + +namespace ARMeilleure.Decoders +{ + class OpCodeT32BImm24 : OpCodeT32, IOpCode32BImm + { + public long Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32BImm24(inst, address, opCode); + + public OpCodeT32BImm24(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + uint pc = GetPc(); + + if (inst.Name == InstName.Blx) + { + pc &= ~3u; + } + + int imm11 = (opCode >> 0) & 0x7ff; + int j2 = (opCode >> 11) & 1; + int j1 = (opCode >> 13) & 1; + int imm10 = (opCode >> 16) & 0x3ff; + int s = (opCode >> 26) & 1; + + int i1 = j1 ^ s ^ 1; + int i2 = j2 ^ s ^ 1; + + int imm32 = imm11 | (imm10 << 11) | (i2 << 21) | (i1 << 22) | (s << 23); + imm32 = (imm32 << 8) >> 7; + + Immediate = pc + imm32; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeT32MemImm12.cs b/src/ARMeilleure/Decoders/OpCodeT32MemImm12.cs new file mode 100644 index 00000000..7838604b --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MemImm12.cs @@ -0,0 +1,25 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32MemImm12 : OpCodeT32, IOpCode32Mem + { + public int Rt { get; } + public int Rn { get; } + public bool WBack => false; + public bool IsLoad { get; } + public bool Index => true; + public bool Add => true; + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemImm12(inst, address, opCode); + + public OpCodeT32MemImm12(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + + Immediate = opCode & 0xfff; + + IsLoad = ((opCode >> 20) & 1) != 0; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeT32MemImm8.cs b/src/ARMeilleure/Decoders/OpCodeT32MemImm8.cs new file mode 100644 index 00000000..d8b7763c --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MemImm8.cs @@ -0,0 +1,29 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32MemImm8 : OpCodeT32, IOpCode32Mem + { + public int Rt { get; } + public int Rn { get; } + public bool WBack { get; } + public bool IsLoad { get; } + public bool Index { get; } + public bool Add { get; } + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemImm8(inst, address, opCode); + + public OpCodeT32MemImm8(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + + Index = ((opCode >> 10) & 1) != 0; + Add = ((opCode >> 9) & 1) != 0; + WBack = ((opCode >> 8) & 1) != 0; + + Immediate = opCode & 0xff; + + IsLoad = ((opCode >> 20) & 1) != 0; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeT32MemImm8D.cs b/src/ARMeilleure/Decoders/OpCodeT32MemImm8D.cs new file mode 100644 index 00000000..7a078c48 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MemImm8D.cs @@ -0,0 +1,31 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32MemImm8D : OpCodeT32, IOpCode32Mem + { + public int Rt { get; } + public int Rt2 { get; } + public int Rn { get; } + public bool WBack { get; } + public bool IsLoad { get; } + public bool Index { get; } + public bool Add { get; } + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemImm8D(inst, address, opCode); + + public OpCodeT32MemImm8D(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt2 = (opCode >> 8) & 0xf; + Rt = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + + Index = ((opCode >> 24) & 1) != 0; + Add = ((opCode >> 23) & 1) != 0; + WBack = ((opCode >> 21) & 1) != 0; + + Immediate = (opCode & 0xff) << 2; + + IsLoad = ((opCode >> 20) & 1) != 0; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeT32MemLdEx.cs b/src/ARMeilleure/Decoders/OpCodeT32MemLdEx.cs new file mode 100644 index 00000000..c8eb36b3 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MemLdEx.cs @@ -0,0 +1,26 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32MemLdEx : OpCodeT32, IOpCode32MemEx + { + public int Rd => 0; + public int Rt { get; } + public int Rt2 { get; } + public int Rn { get; } + + public bool WBack => false; + public bool IsLoad => true; + public bool Index => false; + public bool Add => false; + + public int Immediate => 0; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemLdEx(inst, address, opCode); + + public OpCodeT32MemLdEx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rt2 = (opCode >> 8) & 0xf; + Rt = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32MemMult.cs b/src/ARMeilleure/Decoders/OpCodeT32MemMult.cs new file mode 100644 index 00000000..a9ba306d --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MemMult.cs @@ -0,0 +1,52 @@ +using System.Numerics; + +namespace ARMeilleure.Decoders +{ + class OpCodeT32MemMult : OpCodeT32, IOpCode32MemMult + { + public int Rn { get; } + + public int RegisterMask { get; } + public int Offset { get; } + public int PostOffset { get; } + + public bool IsLoad { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemMult(inst, address, opCode); + + public OpCodeT32MemMult(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rn = (opCode >> 16) & 0xf; + + bool isLoad = (opCode & (1 << 20)) != 0; + bool w = (opCode & (1 << 21)) != 0; + bool u = (opCode & (1 << 23)) != 0; + bool p = (opCode & (1 << 24)) != 0; + + RegisterMask = opCode & 0xffff; + + int regsSize = BitOperations.PopCount((uint)RegisterMask) * 4; + + if (!u) + { + Offset -= regsSize; + } + + if (u == p) + { + Offset += 4; + } + + if (w) + { + PostOffset = u ? regsSize : -regsSize; + } + else + { + PostOffset = 0; + } + + IsLoad = isLoad; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeT32MemRsImm.cs b/src/ARMeilleure/Decoders/OpCodeT32MemRsImm.cs new file mode 100644 index 00000000..056d3b46 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MemRsImm.cs @@ -0,0 +1,30 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32MemRsImm : OpCodeT32, IOpCode32MemRsImm + { + public int Rt { get; } + public int Rn { get; } + public int Rm { get; } + public ShiftType ShiftType => ShiftType.Lsl; + + public bool WBack => false; + public bool IsLoad { get; } + public bool Index => true; + public bool Add => true; + + public int Immediate { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemRsImm(inst, address, opCode); + + public OpCodeT32MemRsImm(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + Rt = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + + IsLoad = (opCode & (1 << 20)) != 0; + + Immediate = (opCode >> 4) & 3; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32MemStEx.cs b/src/ARMeilleure/Decoders/OpCodeT32MemStEx.cs new file mode 100644 index 00000000..6a0a6bb1 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MemStEx.cs @@ -0,0 +1,27 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32MemStEx : OpCodeT32, IOpCode32MemEx + { + public int Rd { get; } + public int Rt { get; } + public int Rt2 { get; } + public int Rn { get; } + + public bool WBack => false; + public bool IsLoad => false; + public bool Index => false; + public bool Add => false; + + public int Immediate => 0; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MemStEx(inst, address, opCode); + + public OpCodeT32MemStEx(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rd = (opCode >> 0) & 0xf; + Rt2 = (opCode >> 8) & 0xf; + Rt = (opCode >> 12) & 0xf; + Rn = (opCode >> 16) & 0xf; + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32MovImm16.cs b/src/ARMeilleure/Decoders/OpCodeT32MovImm16.cs new file mode 100644 index 00000000..5161892b --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32MovImm16.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32MovImm16 : OpCodeT32Alu, IOpCode32AluImm16 + { + public int Immediate { get; } + + public bool IsRotated => false; + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32MovImm16(inst, address, opCode); + + public OpCodeT32MovImm16(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Immediate = (opCode & 0xff) | ((opCode >> 4) & 0x700) | ((opCode >> 15) & 0x800) | ((opCode >> 4) & 0xf000); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeT32ShiftReg.cs b/src/ARMeilleure/Decoders/OpCodeT32ShiftReg.cs new file mode 100644 index 00000000..36055975 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32ShiftReg.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32ShiftReg : OpCodeT32Alu, IOpCode32AluRsReg + { + public int Rm => Rn; + public int Rs { get; } + + public ShiftType ShiftType { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32ShiftReg(inst, address, opCode); + + public OpCodeT32ShiftReg(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rs = (opCode >> 0) & 0xf; + + ShiftType = (ShiftType)((opCode >> 21) & 3); + } + } +} diff --git a/src/ARMeilleure/Decoders/OpCodeT32Tb.cs b/src/ARMeilleure/Decoders/OpCodeT32Tb.cs new file mode 100644 index 00000000..527754b1 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeT32Tb.cs @@ -0,0 +1,16 @@ +namespace ARMeilleure.Decoders +{ + class OpCodeT32Tb : OpCodeT32, IOpCode32BReg + { + public int Rm { get; } + public int Rn { get; } + + public new static OpCode Create(InstDescriptor inst, ulong address, int opCode) => new OpCodeT32Tb(inst, address, opCode); + + public OpCodeT32Tb(InstDescriptor inst, ulong address, int opCode) : base(inst, address, opCode) + { + Rm = (opCode >> 0) & 0xf; + Rn = (opCode >> 16) & 0xf; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/OpCodeTable.cs b/src/ARMeilleure/Decoders/OpCodeTable.cs new file mode 100644 index 00000000..4f359958 --- /dev/null +++ b/src/ARMeilleure/Decoders/OpCodeTable.cs @@ -0,0 +1,1509 @@ +using ARMeilleure.Instructions; +using System; +using System.Collections.Generic; +using System.Numerics; + +namespace ARMeilleure.Decoders +{ + static class OpCodeTable + { + public delegate OpCode MakeOp(InstDescriptor inst, ulong address, int opCode); + + private const int FastLookupSize = 0x1000; + + private readonly struct InstInfo + { + public int Mask { get; } + public int Value { get; } + + public InstDescriptor Inst { get; } + + public MakeOp MakeOp { get; } + + public InstInfo(int mask, int value, InstDescriptor inst, MakeOp makeOp) + { + Mask = mask; + Value = value; + Inst = inst; + MakeOp = makeOp; + } + } + + private static List AllInstA32 = new(); + private static List AllInstT32 = new(); + private static List AllInstA64 = new(); + + private static InstInfo[][] InstA32FastLookup = new InstInfo[FastLookupSize][]; + private static InstInfo[][] InstT32FastLookup = new InstInfo[FastLookupSize][]; + private static InstInfo[][] InstA64FastLookup = new InstInfo[FastLookupSize][]; + + static OpCodeTable() + { +#region "OpCode Table (AArch64)" + // Base + SetA64("x0011010000xxxxx000000xxxxxxxxxx", InstName.Adc, InstEmit.Adc, OpCodeAluRs.Create); + SetA64("x0111010000xxxxx000000xxxxxxxxxx", InstName.Adcs, InstEmit.Adcs, OpCodeAluRs.Create); + SetA64("x00100010xxxxxxxxxxxxxxxxxxxxxxx", InstName.Add, InstEmit.Add, OpCodeAluImm.Create); + SetA64("00001011<<0xxxxx0xxxxxxxxxxxxxxx", InstName.Add, InstEmit.Add, OpCodeAluRs.Create); + SetA64("10001011<<0xxxxxxxxxxxxxxxxxxxxx", InstName.Add, InstEmit.Add, OpCodeAluRs.Create); + SetA64("x0001011001xxxxxxxx0xxxxxxxxxxxx", InstName.Add, InstEmit.Add, OpCodeAluRx.Create); + SetA64("x0001011001xxxxxxxx100xxxxxxxxxx", InstName.Add, InstEmit.Add, OpCodeAluRx.Create); + SetA64("x01100010xxxxxxxxxxxxxxxxxxxxxxx", InstName.Adds, InstEmit.Adds, OpCodeAluImm.Create); + SetA64("00101011<<0xxxxx0xxxxxxxxxxxxxxx", InstName.Adds, InstEmit.Adds, OpCodeAluRs.Create); + SetA64("10101011<<0xxxxxxxxxxxxxxxxxxxxx", InstName.Adds, InstEmit.Adds, OpCodeAluRs.Create); + SetA64("x0101011001xxxxxxxx0xxxxxxxxxxxx", InstName.Adds, InstEmit.Adds, OpCodeAluRx.Create); + SetA64("x0101011001xxxxxxxx100xxxxxxxxxx", InstName.Adds, InstEmit.Adds, OpCodeAluRx.Create); + SetA64("0xx10000xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Adr, InstEmit.Adr, OpCodeAdr.Create); + SetA64("1xx10000xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Adrp, InstEmit.Adrp, OpCodeAdr.Create); + SetA64("0001001000xxxxxxxxxxxxxxxxxxxxxx", InstName.And, InstEmit.And, OpCodeAluImm.Create); + SetA64("100100100xxxxxxxxxxxxxxxxxxxxxxx", InstName.And, InstEmit.And, OpCodeAluImm.Create); + SetA64("00001010xx0xxxxx0xxxxxxxxxxxxxxx", InstName.And, InstEmit.And, OpCodeAluRs.Create); + SetA64("10001010xx0xxxxxxxxxxxxxxxxxxxxx", InstName.And, InstEmit.And, OpCodeAluRs.Create); + SetA64("0111001000xxxxxxxxxxxxxxxxxxxxxx", InstName.Ands, InstEmit.Ands, OpCodeAluImm.Create); + SetA64("111100100xxxxxxxxxxxxxxxxxxxxxxx", InstName.Ands, InstEmit.Ands, OpCodeAluImm.Create); + SetA64("01101010xx0xxxxx0xxxxxxxxxxxxxxx", InstName.Ands, InstEmit.Ands, OpCodeAluRs.Create); + SetA64("11101010xx0xxxxxxxxxxxxxxxxxxxxx", InstName.Ands, InstEmit.Ands, OpCodeAluRs.Create); + SetA64("x0011010110xxxxx001010xxxxxxxxxx", InstName.Asrv, InstEmit.Asrv, OpCodeAluRs.Create); + SetA64("000101xxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.B, InstEmit.B, OpCodeBImmAl.Create); + SetA64("01010100xxxxxxxxxxxxxxxxxxx0xxxx", InstName.B_Cond, InstEmit.B_Cond, OpCodeBImmCond.Create); + SetA64("00110011000xxxxx0xxxxxxxxxxxxxxx", InstName.Bfm, InstEmit.Bfm, OpCodeBfm.Create); + SetA64("1011001101xxxxxxxxxxxxxxxxxxxxxx", InstName.Bfm, InstEmit.Bfm, OpCodeBfm.Create); + SetA64("00001010xx1xxxxx0xxxxxxxxxxxxxxx", InstName.Bic, InstEmit.Bic, OpCodeAluRs.Create); + SetA64("10001010xx1xxxxxxxxxxxxxxxxxxxxx", InstName.Bic, InstEmit.Bic, OpCodeAluRs.Create); + SetA64("01101010xx1xxxxx0xxxxxxxxxxxxxxx", InstName.Bics, InstEmit.Bics, OpCodeAluRs.Create); + SetA64("11101010xx1xxxxxxxxxxxxxxxxxxxxx", InstName.Bics, InstEmit.Bics, OpCodeAluRs.Create); + SetA64("100101xxxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Bl, InstEmit.Bl, OpCodeBImmAl.Create); + SetA64("1101011000111111000000xxxxx00000", InstName.Blr, InstEmit.Blr, OpCodeBReg.Create); + SetA64("1101011000011111000000xxxxx00000", InstName.Br, InstEmit.Br, OpCodeBReg.Create); + SetA64("11010100001xxxxxxxxxxxxxxxx00000", InstName.Brk, InstEmit.Brk, OpCodeException.Create); + SetA64("x0110101xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Cbnz, InstEmit.Cbnz, OpCodeBImmCmp.Create); + SetA64("x0110100xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Cbz, InstEmit.Cbz, OpCodeBImmCmp.Create); + SetA64("x0111010010xxxxxxxxx10xxxxx0xxxx", InstName.Ccmn, InstEmit.Ccmn, OpCodeCcmpImm.Create); + SetA64("x0111010010xxxxxxxxx00xxxxx0xxxx", InstName.Ccmn, InstEmit.Ccmn, OpCodeCcmpReg.Create); + SetA64("x1111010010xxxxxxxxx10xxxxx0xxxx", InstName.Ccmp, InstEmit.Ccmp, OpCodeCcmpImm.Create); + SetA64("x1111010010xxxxxxxxx00xxxxx0xxxx", InstName.Ccmp, InstEmit.Ccmp, OpCodeCcmpReg.Create); + SetA64("11010101000000110011xxxx01011111", InstName.Clrex, InstEmit.Clrex, OpCodeSystem.Create); + SetA64("x101101011000000000101xxxxxxxxxx", InstName.Cls, InstEmit.Cls, OpCodeAlu.Create); + SetA64("x101101011000000000100xxxxxxxxxx", InstName.Clz, InstEmit.Clz, OpCodeAlu.Create); + SetA64("00011010110xxxxx010000xxxxxxxxxx", InstName.Crc32b, InstEmit.Crc32b, OpCodeAluBinary.Create); + SetA64("00011010110xxxxx010001xxxxxxxxxx", InstName.Crc32h, InstEmit.Crc32h, OpCodeAluBinary.Create); + SetA64("00011010110xxxxx010010xxxxxxxxxx", InstName.Crc32w, InstEmit.Crc32w, OpCodeAluBinary.Create); + SetA64("10011010110xxxxx010011xxxxxxxxxx", InstName.Crc32x, InstEmit.Crc32x, OpCodeAluBinary.Create); + SetA64("00011010110xxxxx010100xxxxxxxxxx", InstName.Crc32cb, InstEmit.Crc32cb, OpCodeAluBinary.Create); + SetA64("00011010110xxxxx010101xxxxxxxxxx", InstName.Crc32ch, InstEmit.Crc32ch, OpCodeAluBinary.Create); + SetA64("00011010110xxxxx010110xxxxxxxxxx", InstName.Crc32cw, InstEmit.Crc32cw, OpCodeAluBinary.Create); + SetA64("10011010110xxxxx010111xxxxxxxxxx", InstName.Crc32cx, InstEmit.Crc32cx, OpCodeAluBinary.Create); + SetA64("11010101000000110010001010011111", InstName.Csdb, InstEmit.Csdb, OpCodeSystem.Create); + SetA64("x0011010100xxxxxxxxx00xxxxxxxxxx", InstName.Csel, InstEmit.Csel, OpCodeCsel.Create); + SetA64("x0011010100xxxxxxxxx01xxxxxxxxxx", InstName.Csinc, InstEmit.Csinc, OpCodeCsel.Create); + SetA64("x1011010100xxxxxxxxx00xxxxxxxxxx", InstName.Csinv, InstEmit.Csinv, OpCodeCsel.Create); + SetA64("x1011010100xxxxxxxxx01xxxxxxxxxx", InstName.Csneg, InstEmit.Csneg, OpCodeCsel.Create); + SetA64("11010101000000110011xxxx10111111", InstName.Dmb, InstEmit.Dmb, OpCodeSystem.Create); + SetA64("11010101000000110011xxxx10011111", InstName.Dsb, InstEmit.Dsb, OpCodeSystem.Create); + SetA64("01001010xx1xxxxx0xxxxxxxxxxxxxxx", InstName.Eon, InstEmit.Eon, OpCodeAluRs.Create); + SetA64("11001010xx1xxxxxxxxxxxxxxxxxxxxx", InstName.Eon, InstEmit.Eon, OpCodeAluRs.Create); + SetA64("0101001000xxxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit.Eor, OpCodeAluImm.Create); + SetA64("110100100xxxxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit.Eor, OpCodeAluImm.Create); + SetA64("01001010xx0xxxxx0xxxxxxxxxxxxxxx", InstName.Eor, InstEmit.Eor, OpCodeAluRs.Create); + SetA64("11001010xx0xxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit.Eor, OpCodeAluRs.Create); + SetA64("00010011100xxxxx0xxxxxxxxxxxxxxx", InstName.Extr, InstEmit.Extr, OpCodeAluRs.Create); + SetA64("10010011110xxxxxxxxxxxxxxxxxxxxx", InstName.Extr, InstEmit.Extr, OpCodeAluRs.Create); + SetA64("11010101000000110010000011011111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint + SetA64("11010101000000110010000011111111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint + SetA64("110101010000001100100001xxx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint + SetA64("1101010100000011001000100xx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint + SetA64("1101010100000011001000101>>11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint + SetA64("110101010000001100100011xxx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint + SetA64("11010101000000110010>>xxxxx11111", InstName.Hint, InstEmit.Nop, OpCodeSystem.Create); // Reserved Hint + SetA64("11010101000000110011xxxx11011111", InstName.Isb, InstEmit.Isb, OpCodeSystem.Create); + SetA64("xx001000110xxxxx1xxxxxxxxxxxxxxx", InstName.Ldar, InstEmit.Ldar, OpCodeMemEx.Create); + SetA64("1x001000011xxxxx1xxxxxxxxxxxxxxx", InstName.Ldaxp, InstEmit.Ldaxp, OpCodeMemEx.Create); + SetA64("xx001000010xxxxx1xxxxxxxxxxxxxxx", InstName.Ldaxr, InstEmit.Ldaxr, OpCodeMemEx.Create); + SetA64("<<10100xx1xxxxxxxxxxxxxxxxxxxxxx", InstName.Ldp, InstEmit.Ldp, OpCodeMemPair.Create); + SetA64("xx111000010xxxxxxxxxxxxxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeMemImm.Create); + SetA64("xx11100101xxxxxxxxxxxxxxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeMemImm.Create); + SetA64("xx111000011xxxxxxxxx10xxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeMemReg.Create); + SetA64("xx011000xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ldr_Literal, InstEmit.Ldr_Literal, OpCodeMemLit.Create); + SetA64("0x1110001x0xxxxxxxxxxxxxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, OpCodeMemImm.Create); + SetA64("0x1110011xxxxxxxxxxxxxxxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, OpCodeMemImm.Create); + SetA64("10111000100xxxxxxxxxxxxxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, OpCodeMemImm.Create); + SetA64("1011100110xxxxxxxxxxxxxxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, OpCodeMemImm.Create); + SetA64("0x1110001x1xxxxxxxxx10xxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, OpCodeMemReg.Create); + SetA64("10111000101xxxxxxxxx10xxxxxxxxxx", InstName.Ldrs, InstEmit.Ldrs, OpCodeMemReg.Create); + SetA64("xx001000010xxxxx0xxxxxxxxxxxxxxx", InstName.Ldxr, InstEmit.Ldxr, OpCodeMemEx.Create); + SetA64("1x001000011xxxxx0xxxxxxxxxxxxxxx", InstName.Ldxp, InstEmit.Ldxp, OpCodeMemEx.Create); + SetA64("x0011010110xxxxx001000xxxxxxxxxx", InstName.Lslv, InstEmit.Lslv, OpCodeAluRs.Create); + SetA64("x0011010110xxxxx001001xxxxxxxxxx", InstName.Lsrv, InstEmit.Lsrv, OpCodeAluRs.Create); + SetA64("x0011011000xxxxx0xxxxxxxxxxxxxxx", InstName.Madd, InstEmit.Madd, OpCodeMul.Create); + SetA64("0111001010xxxxxxxxxxxxxxxxxxxxxx", InstName.Movk, InstEmit.Movk, OpCodeMov.Create); + SetA64("111100101xxxxxxxxxxxxxxxxxxxxxxx", InstName.Movk, InstEmit.Movk, OpCodeMov.Create); + SetA64("0001001010xxxxxxxxxxxxxxxxxxxxxx", InstName.Movn, InstEmit.Movn, OpCodeMov.Create); + SetA64("100100101xxxxxxxxxxxxxxxxxxxxxxx", InstName.Movn, InstEmit.Movn, OpCodeMov.Create); + SetA64("0101001010xxxxxxxxxxxxxxxxxxxxxx", InstName.Movz, InstEmit.Movz, OpCodeMov.Create); + SetA64("110100101xxxxxxxxxxxxxxxxxxxxxxx", InstName.Movz, InstEmit.Movz, OpCodeMov.Create); + SetA64("110101010011xxxxxxxxxxxxxxxxxxxx", InstName.Mrs, InstEmit.Mrs, OpCodeSystem.Create); + SetA64("110101010001xxxxxxxxxxxxxxxxxxxx", InstName.Msr, InstEmit.Msr, OpCodeSystem.Create); + SetA64("x0011011000xxxxx1xxxxxxxxxxxxxxx", InstName.Msub, InstEmit.Msub, OpCodeMul.Create); + SetA64("11010101000000110010000000011111", InstName.Nop, InstEmit.Nop, OpCodeSystem.Create); + SetA64("00101010xx1xxxxx0xxxxxxxxxxxxxxx", InstName.Orn, InstEmit.Orn, OpCodeAluRs.Create); + SetA64("10101010xx1xxxxxxxxxxxxxxxxxxxxx", InstName.Orn, InstEmit.Orn, OpCodeAluRs.Create); + SetA64("0011001000xxxxxxxxxxxxxxxxxxxxxx", InstName.Orr, InstEmit.Orr, OpCodeAluImm.Create); + SetA64("101100100xxxxxxxxxxxxxxxxxxxxxxx", InstName.Orr, InstEmit.Orr, OpCodeAluImm.Create); + SetA64("00101010xx0xxxxx0xxxxxxxxxxxxxxx", InstName.Orr, InstEmit.Orr, OpCodeAluRs.Create); + SetA64("10101010xx0xxxxxxxxxxxxxxxxxxxxx", InstName.Orr, InstEmit.Orr, OpCodeAluRs.Create); + SetA64("1111100110xxxxxxxxxxxxxxxxxxxxxx", InstName.Prfm, InstEmit.Prfm, OpCodeMemImm.Create); // immediate + SetA64("11111000100xxxxxxxxx00xxxxxxxxxx", InstName.Prfm, InstEmit.Prfm, OpCodeMemImm.Create); // prfum (unscaled offset) + SetA64("11011000xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Prfm, InstEmit.Prfm, OpCodeMemLit.Create); // literal + SetA64("11111000101xxxxxxxxx10xxxxxxxxxx", InstName.Prfm, InstEmit.Prfm, OpCodeMemReg.Create); // register + SetA64("x101101011000000000000xxxxxxxxxx", InstName.Rbit, InstEmit.Rbit, OpCodeAlu.Create); + SetA64("1101011001011111000000xxxxx00000", InstName.Ret, InstEmit.Ret, OpCodeBReg.Create); + SetA64("x101101011000000000001xxxxxxxxxx", InstName.Rev16, InstEmit.Rev16, OpCodeAlu.Create); + SetA64("x101101011000000000010xxxxxxxxxx", InstName.Rev32, InstEmit.Rev32, OpCodeAlu.Create); + SetA64("1101101011000000000011xxxxxxxxxx", InstName.Rev64, InstEmit.Rev64, OpCodeAlu.Create); + SetA64("x0011010110xxxxx001011xxxxxxxxxx", InstName.Rorv, InstEmit.Rorv, OpCodeAluRs.Create); + SetA64("x1011010000xxxxx000000xxxxxxxxxx", InstName.Sbc, InstEmit.Sbc, OpCodeAluRs.Create); + SetA64("x1111010000xxxxx000000xxxxxxxxxx", InstName.Sbcs, InstEmit.Sbcs, OpCodeAluRs.Create); + SetA64("00010011000xxxxx0xxxxxxxxxxxxxxx", InstName.Sbfm, InstEmit.Sbfm, OpCodeBfm.Create); + SetA64("1001001101xxxxxxxxxxxxxxxxxxxxxx", InstName.Sbfm, InstEmit.Sbfm, OpCodeBfm.Create); + SetA64("x0011010110xxxxx000011xxxxxxxxxx", InstName.Sdiv, InstEmit.Sdiv, OpCodeAluBinary.Create); + SetA64("11010101000000110010000010011111", InstName.Sev, InstEmit.Nop, OpCodeSystem.Create); + SetA64("11010101000000110010000010111111", InstName.Sevl, InstEmit.Nop, OpCodeSystem.Create); + SetA64("10011011001xxxxx0xxxxxxxxxxxxxxx", InstName.Smaddl, InstEmit.Smaddl, OpCodeMul.Create); + SetA64("10011011001xxxxx1xxxxxxxxxxxxxxx", InstName.Smsubl, InstEmit.Smsubl, OpCodeMul.Create); + SetA64("10011011010xxxxx0xxxxxxxxxxxxxxx", InstName.Smulh, InstEmit.Smulh, OpCodeMul.Create); + SetA64("xx001000100xxxxx1xxxxxxxxxxxxxxx", InstName.Stlr, InstEmit.Stlr, OpCodeMemEx.Create); + SetA64("1x001000001xxxxx1xxxxxxxxxxxxxxx", InstName.Stlxp, InstEmit.Stlxp, OpCodeMemEx.Create); + SetA64("xx001000000xxxxx1xxxxxxxxxxxxxxx", InstName.Stlxr, InstEmit.Stlxr, OpCodeMemEx.Create); + SetA64("x010100xx0xxxxxxxxxxxxxxxxxxxxxx", InstName.Stp, InstEmit.Stp, OpCodeMemPair.Create); + SetA64("xx111000000xxxxxxxxxxxxxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeMemImm.Create); + SetA64("xx11100100xxxxxxxxxxxxxxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeMemImm.Create); + SetA64("xx111000001xxxxxxxxx10xxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeMemReg.Create); + SetA64("1x001000001xxxxx0xxxxxxxxxxxxxxx", InstName.Stxp, InstEmit.Stxp, OpCodeMemEx.Create); + SetA64("xx001000000xxxxx0xxxxxxxxxxxxxxx", InstName.Stxr, InstEmit.Stxr, OpCodeMemEx.Create); + SetA64("x10100010xxxxxxxxxxxxxxxxxxxxxxx", InstName.Sub, InstEmit.Sub, OpCodeAluImm.Create); + SetA64("01001011<<0xxxxx0xxxxxxxxxxxxxxx", InstName.Sub, InstEmit.Sub, OpCodeAluRs.Create); + SetA64("11001011<<0xxxxxxxxxxxxxxxxxxxxx", InstName.Sub, InstEmit.Sub, OpCodeAluRs.Create); + SetA64("x1001011001xxxxxxxx0xxxxxxxxxxxx", InstName.Sub, InstEmit.Sub, OpCodeAluRx.Create); + SetA64("x1001011001xxxxxxxx100xxxxxxxxxx", InstName.Sub, InstEmit.Sub, OpCodeAluRx.Create); + SetA64("x11100010xxxxxxxxxxxxxxxxxxxxxxx", InstName.Subs, InstEmit.Subs, OpCodeAluImm.Create); + SetA64("01101011<<0xxxxx0xxxxxxxxxxxxxxx", InstName.Subs, InstEmit.Subs, OpCodeAluRs.Create); + SetA64("11101011<<0xxxxxxxxxxxxxxxxxxxxx", InstName.Subs, InstEmit.Subs, OpCodeAluRs.Create); + SetA64("x1101011001xxxxxxxx0xxxxxxxxxxxx", InstName.Subs, InstEmit.Subs, OpCodeAluRx.Create); + SetA64("x1101011001xxxxxxxx100xxxxxxxxxx", InstName.Subs, InstEmit.Subs, OpCodeAluRx.Create); + SetA64("11010100000xxxxxxxxxxxxxxxx00001", InstName.Svc, InstEmit.Svc, OpCodeException.Create); + SetA64("1101010100001xxxxxxxxxxxxxxxxxxx", InstName.Sys, InstEmit.Sys, OpCodeSystem.Create); + SetA64("x0110111xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Tbnz, InstEmit.Tbnz, OpCodeBImmTest.Create); + SetA64("x0110110xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Tbz, InstEmit.Tbz, OpCodeBImmTest.Create); + SetA64("01010011000xxxxx0xxxxxxxxxxxxxxx", InstName.Ubfm, InstEmit.Ubfm, OpCodeBfm.Create); + SetA64("1101001101xxxxxxxxxxxxxxxxxxxxxx", InstName.Ubfm, InstEmit.Ubfm, OpCodeBfm.Create); + SetA64("x0011010110xxxxx000010xxxxxxxxxx", InstName.Udiv, InstEmit.Udiv, OpCodeAluBinary.Create); + SetA64("10011011101xxxxx0xxxxxxxxxxxxxxx", InstName.Umaddl, InstEmit.Umaddl, OpCodeMul.Create); + SetA64("10011011101xxxxx1xxxxxxxxxxxxxxx", InstName.Umsubl, InstEmit.Umsubl, OpCodeMul.Create); + SetA64("10011011110xxxxx0xxxxxxxxxxxxxxx", InstName.Umulh, InstEmit.Umulh, OpCodeMul.Create); + SetA64("11010101000000110010000001011111", InstName.Wfe, InstEmit.Nop, OpCodeSystem.Create); + SetA64("11010101000000110010000001111111", InstName.Wfi, InstEmit.Nop, OpCodeSystem.Create); + SetA64("11010101000000110010000000111111", InstName.Yield, InstEmit.Nop, OpCodeSystem.Create); + + // FP & SIMD + SetA64("0101111011100000101110xxxxxxxxxx", InstName.Abs_S, InstEmit.Abs_S, OpCodeSimd.Create); + SetA64("0>001110<<100000101110xxxxxxxxxx", InstName.Abs_V, InstEmit.Abs_V, OpCodeSimd.Create); + SetA64("01011110111xxxxx100001xxxxxxxxxx", InstName.Add_S, InstEmit.Add_S, OpCodeSimdReg.Create); + SetA64("0>001110<<1xxxxx100001xxxxxxxxxx", InstName.Add_V, InstEmit.Add_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx010000xxxxxxxxxx", InstName.Addhn_V, InstEmit.Addhn_V, OpCodeSimdReg.Create); + SetA64("0101111011110001101110xxxxxxxxxx", InstName.Addp_S, InstEmit.Addp_S, OpCodeSimd.Create); + SetA64("0>001110<<1xxxxx101111xxxxxxxxxx", InstName.Addp_V, InstEmit.Addp_V, OpCodeSimdReg.Create); + SetA64("000011100x110001101110xxxxxxxxxx", InstName.Addv_V, InstEmit.Addv_V, OpCodeSimd.Create); + SetA64("01001110<<110001101110xxxxxxxxxx", InstName.Addv_V, InstEmit.Addv_V, OpCodeSimd.Create); + SetA64("0100111000101000010110xxxxxxxxxx", InstName.Aesd_V, InstEmit.Aesd_V, OpCodeSimd.Create); + SetA64("0100111000101000010010xxxxxxxxxx", InstName.Aese_V, InstEmit.Aese_V, OpCodeSimd.Create); + SetA64("0100111000101000011110xxxxxxxxxx", InstName.Aesimc_V, InstEmit.Aesimc_V, OpCodeSimd.Create); + SetA64("0100111000101000011010xxxxxxxxxx", InstName.Aesmc_V, InstEmit.Aesmc_V, OpCodeSimd.Create); + SetA64("0x001110001xxxxx000111xxxxxxxxxx", InstName.And_V, InstEmit.And_V, OpCodeSimdReg.Create); + SetA64("0x001110011xxxxx000111xxxxxxxxxx", InstName.Bic_V, InstEmit.Bic_V, OpCodeSimdReg.Create); + SetA64("0x10111100000xxx0xx101xxxxxxxxxx", InstName.Bic_Vi, InstEmit.Bic_Vi, OpCodeSimdImm.Create); + SetA64("0x10111100000xxx10x101xxxxxxxxxx", InstName.Bic_Vi, InstEmit.Bic_Vi, OpCodeSimdImm.Create); + SetA64("0x101110111xxxxx000111xxxxxxxxxx", InstName.Bif_V, InstEmit.Bif_V, OpCodeSimdReg.Create); + SetA64("0x101110101xxxxx000111xxxxxxxxxx", InstName.Bit_V, InstEmit.Bit_V, OpCodeSimdReg.Create); + SetA64("0x101110011xxxxx000111xxxxxxxxxx", InstName.Bsl_V, InstEmit.Bsl_V, OpCodeSimdReg.Create); + SetA64("0x001110<<100000010010xxxxxxxxxx", InstName.Cls_V, InstEmit.Cls_V, OpCodeSimd.Create); + SetA64("0x101110<<100000010010xxxxxxxxxx", InstName.Clz_V, InstEmit.Clz_V, OpCodeSimd.Create); + SetA64("01111110111xxxxx100011xxxxxxxxxx", InstName.Cmeq_S, InstEmit.Cmeq_S, OpCodeSimdReg.Create); + SetA64("0101111011100000100110xxxxxxxxxx", InstName.Cmeq_S, InstEmit.Cmeq_S, OpCodeSimd.Create); + SetA64("0>101110<<1xxxxx100011xxxxxxxxxx", InstName.Cmeq_V, InstEmit.Cmeq_V, OpCodeSimdReg.Create); + SetA64("0>001110<<100000100110xxxxxxxxxx", InstName.Cmeq_V, InstEmit.Cmeq_V, OpCodeSimd.Create); + SetA64("01011110111xxxxx001111xxxxxxxxxx", InstName.Cmge_S, InstEmit.Cmge_S, OpCodeSimdReg.Create); + SetA64("0111111011100000100010xxxxxxxxxx", InstName.Cmge_S, InstEmit.Cmge_S, OpCodeSimd.Create); + SetA64("0>001110<<1xxxxx001111xxxxxxxxxx", InstName.Cmge_V, InstEmit.Cmge_V, OpCodeSimdReg.Create); + SetA64("0>101110<<100000100010xxxxxxxxxx", InstName.Cmge_V, InstEmit.Cmge_V, OpCodeSimd.Create); + SetA64("01011110111xxxxx001101xxxxxxxxxx", InstName.Cmgt_S, InstEmit.Cmgt_S, OpCodeSimdReg.Create); + SetA64("0101111011100000100010xxxxxxxxxx", InstName.Cmgt_S, InstEmit.Cmgt_S, OpCodeSimd.Create); + SetA64("0>001110<<1xxxxx001101xxxxxxxxxx", InstName.Cmgt_V, InstEmit.Cmgt_V, OpCodeSimdReg.Create); + SetA64("0>001110<<100000100010xxxxxxxxxx", InstName.Cmgt_V, InstEmit.Cmgt_V, OpCodeSimd.Create); + SetA64("01111110111xxxxx001101xxxxxxxxxx", InstName.Cmhi_S, InstEmit.Cmhi_S, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx001101xxxxxxxxxx", InstName.Cmhi_V, InstEmit.Cmhi_V, OpCodeSimdReg.Create); + SetA64("01111110111xxxxx001111xxxxxxxxxx", InstName.Cmhs_S, InstEmit.Cmhs_S, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx001111xxxxxxxxxx", InstName.Cmhs_V, InstEmit.Cmhs_V, OpCodeSimdReg.Create); + SetA64("0111111011100000100110xxxxxxxxxx", InstName.Cmle_S, InstEmit.Cmle_S, OpCodeSimd.Create); + SetA64("0>101110<<100000100110xxxxxxxxxx", InstName.Cmle_V, InstEmit.Cmle_V, OpCodeSimd.Create); + SetA64("0101111011100000101010xxxxxxxxxx", InstName.Cmlt_S, InstEmit.Cmlt_S, OpCodeSimd.Create); + SetA64("0>001110<<100000101010xxxxxxxxxx", InstName.Cmlt_V, InstEmit.Cmlt_V, OpCodeSimd.Create); + SetA64("01011110111xxxxx100011xxxxxxxxxx", InstName.Cmtst_S, InstEmit.Cmtst_S, OpCodeSimdReg.Create); + SetA64("0>001110<<1xxxxx100011xxxxxxxxxx", InstName.Cmtst_V, InstEmit.Cmtst_V, OpCodeSimdReg.Create); + SetA64("0x00111000100000010110xxxxxxxxxx", InstName.Cnt_V, InstEmit.Cnt_V, OpCodeSimd.Create); + SetA64("0>001110000x<>>>000011xxxxxxxxxx", InstName.Dup_Gp, InstEmit.Dup_Gp, OpCodeSimdIns.Create); + SetA64("01011110000xxxxx000001xxxxxxxxxx", InstName.Dup_S, InstEmit.Dup_S, OpCodeSimdIns.Create); + SetA64("0>001110000x<>>>000001xxxxxxxxxx", InstName.Dup_V, InstEmit.Dup_V, OpCodeSimdIns.Create); + SetA64("0x101110001xxxxx000111xxxxxxxxxx", InstName.Eor_V, InstEmit.Eor_V, OpCodeSimdReg.Create); + SetA64("0>101110000xxxxx01011101<1xxxxx110101xxxxxxxxxx", InstName.Fabd_V, InstEmit.Fabd_V, OpCodeSimdReg.Create); + SetA64("000111100x100000110000xxxxxxxxxx", InstName.Fabs_S, InstEmit.Fabs_S, OpCodeSimd.Create); + SetA64("0>0011101<100000111110xxxxxxxxxx", InstName.Fabs_V, InstEmit.Fabs_V, OpCodeSimd.Create); + SetA64("011111100x1xxxxx111011xxxxxxxxxx", InstName.Facge_S, InstEmit.Facge_S, OpCodeSimdReg.Create); + SetA64("0>1011100<1xxxxx111011xxxxxxxxxx", InstName.Facge_V, InstEmit.Facge_V, OpCodeSimdReg.Create); + SetA64("011111101x1xxxxx111011xxxxxxxxxx", InstName.Facgt_S, InstEmit.Facgt_S, OpCodeSimdReg.Create); + SetA64("0>1011101<1xxxxx111011xxxxxxxxxx", InstName.Facgt_V, InstEmit.Facgt_V, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxx001010xxxxxxxxxx", InstName.Fadd_S, InstEmit.Fadd_S, OpCodeSimdReg.Create); + SetA64("0>0011100<1xxxxx110101xxxxxxxxxx", InstName.Fadd_V, InstEmit.Fadd_V, OpCodeSimdReg.Create); + SetA64("011111100x110000110110xxxxxxxxxx", InstName.Faddp_S, InstEmit.Faddp_S, OpCodeSimd.Create); + SetA64("0>1011100<1xxxxx110101xxxxxxxxxx", InstName.Faddp_V, InstEmit.Faddp_V, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxxxxxx01xxxxx0xxxx", InstName.Fccmp_S, InstEmit.Fccmp_S, OpCodeSimdFcond.Create); + SetA64("000111100x1xxxxxxxxx01xxxxx1xxxx", InstName.Fccmpe_S, InstEmit.Fccmpe_S, OpCodeSimdFcond.Create); + SetA64("010111100x1xxxxx111001xxxxxxxxxx", InstName.Fcmeq_S, InstEmit.Fcmeq_S, OpCodeSimdReg.Create); + SetA64("010111101x100000110110xxxxxxxxxx", InstName.Fcmeq_S, InstEmit.Fcmeq_S, OpCodeSimd.Create); + SetA64("0>0011100<1xxxxx111001xxxxxxxxxx", InstName.Fcmeq_V, InstEmit.Fcmeq_V, OpCodeSimdReg.Create); + SetA64("0>0011101<100000110110xxxxxxxxxx", InstName.Fcmeq_V, InstEmit.Fcmeq_V, OpCodeSimd.Create); + SetA64("011111100x1xxxxx111001xxxxxxxxxx", InstName.Fcmge_S, InstEmit.Fcmge_S, OpCodeSimdReg.Create); + SetA64("011111101x100000110010xxxxxxxxxx", InstName.Fcmge_S, InstEmit.Fcmge_S, OpCodeSimd.Create); + SetA64("0>1011100<1xxxxx111001xxxxxxxxxx", InstName.Fcmge_V, InstEmit.Fcmge_V, OpCodeSimdReg.Create); + SetA64("0>1011101<100000110010xxxxxxxxxx", InstName.Fcmge_V, InstEmit.Fcmge_V, OpCodeSimd.Create); + SetA64("011111101x1xxxxx111001xxxxxxxxxx", InstName.Fcmgt_S, InstEmit.Fcmgt_S, OpCodeSimdReg.Create); + SetA64("010111101x100000110010xxxxxxxxxx", InstName.Fcmgt_S, InstEmit.Fcmgt_S, OpCodeSimd.Create); + SetA64("0>1011101<1xxxxx111001xxxxxxxxxx", InstName.Fcmgt_V, InstEmit.Fcmgt_V, OpCodeSimdReg.Create); + SetA64("0>0011101<100000110010xxxxxxxxxx", InstName.Fcmgt_V, InstEmit.Fcmgt_V, OpCodeSimd.Create); + SetA64("011111101x100000110110xxxxxxxxxx", InstName.Fcmle_S, InstEmit.Fcmle_S, OpCodeSimd.Create); + SetA64("0>1011101<100000110110xxxxxxxxxx", InstName.Fcmle_V, InstEmit.Fcmle_V, OpCodeSimd.Create); + SetA64("010111101x100000111010xxxxxxxxxx", InstName.Fcmlt_S, InstEmit.Fcmlt_S, OpCodeSimd.Create); + SetA64("0>0011101<100000111010xxxxxxxxxx", InstName.Fcmlt_V, InstEmit.Fcmlt_V, OpCodeSimd.Create); + SetA64("000111100x1xxxxx001000xxxxx0x000", InstName.Fcmp_S, InstEmit.Fcmp_S, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxx001000xxxxx1x000", InstName.Fcmpe_S, InstEmit.Fcmpe_S, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxxxxxx11xxxxxxxxxx", InstName.Fcsel_S, InstEmit.Fcsel_S, OpCodeSimdFcond.Create); + SetA64("00011110xx10001xx10000xxxxxxxxxx", InstName.Fcvt_S, InstEmit.Fcvt_S, OpCodeSimd.Create); + SetA64("x00111100x100100000000xxxxxxxxxx", InstName.Fcvtas_Gp, InstEmit.Fcvtas_Gp, OpCodeSimdCvt.Create); + SetA64("010111100x100001110010xxxxxxxxxx", InstName.Fcvtas_S, InstEmit.Fcvtas_S, OpCodeSimd.Create); + SetA64("0>0011100<100001110010xxxxxxxxxx", InstName.Fcvtas_V, InstEmit.Fcvtas_V, OpCodeSimd.Create); + SetA64("x00111100x100101000000xxxxxxxxxx", InstName.Fcvtau_Gp, InstEmit.Fcvtau_Gp, OpCodeSimdCvt.Create); + SetA64("011111100x100001110010xxxxxxxxxx", InstName.Fcvtau_S, InstEmit.Fcvtau_S, OpCodeSimd.Create); + SetA64("0>1011100<100001110010xxxxxxxxxx", InstName.Fcvtau_V, InstEmit.Fcvtau_V, OpCodeSimd.Create); + SetA64("0x0011100x100001011110xxxxxxxxxx", InstName.Fcvtl_V, InstEmit.Fcvtl_V, OpCodeSimd.Create); + SetA64("x00111100x110000000000xxxxxxxxxx", InstName.Fcvtms_Gp, InstEmit.Fcvtms_Gp, OpCodeSimdCvt.Create); + SetA64("0>0011100<100001101110xxxxxxxxxx", InstName.Fcvtms_V, InstEmit.Fcvtms_V, OpCodeSimd.Create); + SetA64("x00111100x110001000000xxxxxxxxxx", InstName.Fcvtmu_Gp, InstEmit.Fcvtmu_Gp, OpCodeSimdCvt.Create); + SetA64("0x0011100x100001011010xxxxxxxxxx", InstName.Fcvtn_V, InstEmit.Fcvtn_V, OpCodeSimd.Create); + SetA64("x00111100x100000000000xxxxxxxxxx", InstName.Fcvtns_Gp, InstEmit.Fcvtns_Gp, OpCodeSimdCvt.Create); + SetA64("010111100x100001101010xxxxxxxxxx", InstName.Fcvtns_S, InstEmit.Fcvtns_S, OpCodeSimd.Create); + SetA64("0>0011100<100001101010xxxxxxxxxx", InstName.Fcvtns_V, InstEmit.Fcvtns_V, OpCodeSimd.Create); + SetA64("011111100x100001101010xxxxxxxxxx", InstName.Fcvtnu_S, InstEmit.Fcvtnu_S, OpCodeSimd.Create); + SetA64("0>1011100<100001101010xxxxxxxxxx", InstName.Fcvtnu_V, InstEmit.Fcvtnu_V, OpCodeSimd.Create); + SetA64("x00111100x101000000000xxxxxxxxxx", InstName.Fcvtps_Gp, InstEmit.Fcvtps_Gp, OpCodeSimdCvt.Create); + SetA64("x00111100x101001000000xxxxxxxxxx", InstName.Fcvtpu_Gp, InstEmit.Fcvtpu_Gp, OpCodeSimdCvt.Create); + SetA64("x00111100x111000000000xxxxxxxxxx", InstName.Fcvtzs_Gp, InstEmit.Fcvtzs_Gp, OpCodeSimdCvt.Create); + SetA64(">00111100x011000>xxxxxxxxxxxxxxx", InstName.Fcvtzs_Gp_Fixed, InstEmit.Fcvtzs_Gp_Fixed, OpCodeSimdCvt.Create); + SetA64("010111101x100001101110xxxxxxxxxx", InstName.Fcvtzs_S, InstEmit.Fcvtzs_S, OpCodeSimd.Create); + SetA64("0>0011101<100001101110xxxxxxxxxx", InstName.Fcvtzs_V, InstEmit.Fcvtzs_V, OpCodeSimd.Create); + SetA64("0x001111001xxxxx111111xxxxxxxxxx", InstName.Fcvtzs_V_Fixed, InstEmit.Fcvtzs_V_Fixed, OpCodeSimdShImm.Create); + SetA64("0100111101xxxxxx111111xxxxxxxxxx", InstName.Fcvtzs_V_Fixed, InstEmit.Fcvtzs_V_Fixed, OpCodeSimdShImm.Create); + SetA64("x00111100x111001000000xxxxxxxxxx", InstName.Fcvtzu_Gp, InstEmit.Fcvtzu_Gp, OpCodeSimdCvt.Create); + SetA64(">00111100x011001>xxxxxxxxxxxxxxx", InstName.Fcvtzu_Gp_Fixed, InstEmit.Fcvtzu_Gp_Fixed, OpCodeSimdCvt.Create); + SetA64("011111101x100001101110xxxxxxxxxx", InstName.Fcvtzu_S, InstEmit.Fcvtzu_S, OpCodeSimd.Create); + SetA64("0>1011101<100001101110xxxxxxxxxx", InstName.Fcvtzu_V, InstEmit.Fcvtzu_V, OpCodeSimd.Create); + SetA64("0x101111001xxxxx111111xxxxxxxxxx", InstName.Fcvtzu_V_Fixed, InstEmit.Fcvtzu_V_Fixed, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx111111xxxxxxxxxx", InstName.Fcvtzu_V_Fixed, InstEmit.Fcvtzu_V_Fixed, OpCodeSimdShImm.Create); + SetA64("000111100x1xxxxx000110xxxxxxxxxx", InstName.Fdiv_S, InstEmit.Fdiv_S, OpCodeSimdReg.Create); + SetA64("0>1011100<1xxxxx111111xxxxxxxxxx", InstName.Fdiv_V, InstEmit.Fdiv_V, OpCodeSimdReg.Create); + SetA64("000111110x0xxxxx0xxxxxxxxxxxxxxx", InstName.Fmadd_S, InstEmit.Fmadd_S, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxx010010xxxxxxxxxx", InstName.Fmax_S, InstEmit.Fmax_S, OpCodeSimdReg.Create); + SetA64("0>0011100<1xxxxx111101xxxxxxxxxx", InstName.Fmax_V, InstEmit.Fmax_V, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxx011010xxxxxxxxxx", InstName.Fmaxnm_S, InstEmit.Fmaxnm_S, OpCodeSimdReg.Create); + SetA64("0>0011100<1xxxxx110001xxxxxxxxxx", InstName.Fmaxnm_V, InstEmit.Fmaxnm_V, OpCodeSimdReg.Create); + SetA64("011111100x110000110010xxxxxxxxxx", InstName.Fmaxnmp_S, InstEmit.Fmaxnmp_S, OpCodeSimd.Create); + SetA64("0>1011100<1xxxxx110001xxxxxxxxxx", InstName.Fmaxnmp_V, InstEmit.Fmaxnmp_V, OpCodeSimdReg.Create); + SetA64("0110111000110000110010xxxxxxxxxx", InstName.Fmaxnmv_V, InstEmit.Fmaxnmv_V, OpCodeSimd.Create); + SetA64("0>1011100<1xxxxx111101xxxxxxxxxx", InstName.Fmaxp_V, InstEmit.Fmaxp_V, OpCodeSimdReg.Create); + SetA64("0110111000110000111110xxxxxxxxxx", InstName.Fmaxv_V, InstEmit.Fmaxv_V, OpCodeSimd.Create); + SetA64("000111100x1xxxxx010110xxxxxxxxxx", InstName.Fmin_S, InstEmit.Fmin_S, OpCodeSimdReg.Create); + SetA64("0>0011101<1xxxxx111101xxxxxxxxxx", InstName.Fmin_V, InstEmit.Fmin_V, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxx011110xxxxxxxxxx", InstName.Fminnm_S, InstEmit.Fminnm_S, OpCodeSimdReg.Create); + SetA64("0>0011101<1xxxxx110001xxxxxxxxxx", InstName.Fminnm_V, InstEmit.Fminnm_V, OpCodeSimdReg.Create); + SetA64("011111101x110000110010xxxxxxxxxx", InstName.Fminnmp_S, InstEmit.Fminnmp_S, OpCodeSimd.Create); + SetA64("0>1011101<1xxxxx110001xxxxxxxxxx", InstName.Fminnmp_V, InstEmit.Fminnmp_V, OpCodeSimdReg.Create); + SetA64("0110111010110000110010xxxxxxxxxx", InstName.Fminnmv_V, InstEmit.Fminnmv_V, OpCodeSimd.Create); + SetA64("0>1011101<1xxxxx111101xxxxxxxxxx", InstName.Fminp_V, InstEmit.Fminp_V, OpCodeSimdReg.Create); + SetA64("0110111010110000111110xxxxxxxxxx", InstName.Fminv_V, InstEmit.Fminv_V, OpCodeSimd.Create); + SetA64("010111111xxxxxxx0001x0xxxxxxxxxx", InstName.Fmla_Se, InstEmit.Fmla_Se, OpCodeSimdRegElemF.Create); + SetA64("0>0011100<1xxxxx110011xxxxxxxxxx", InstName.Fmla_V, InstEmit.Fmla_V, OpCodeSimdReg.Create); + SetA64("0>00111110011101<1xxxxx110011xxxxxxxxxx", InstName.Fmls_V, InstEmit.Fmls_V, OpCodeSimdReg.Create); + SetA64("0>00111111011100<1xxxxx110111xxxxxxxxxx", InstName.Fmul_V, InstEmit.Fmul_V, OpCodeSimdReg.Create); + SetA64("0>00111110011100<1xxxxx110111xxxxxxxxxx", InstName.Fmulx_V, InstEmit.Fmulx_V, OpCodeSimdReg.Create); + SetA64("0>10111111011101<100000111110xxxxxxxxxx", InstName.Fneg_V, InstEmit.Fneg_V, OpCodeSimd.Create); + SetA64("000111110x1xxxxx0xxxxxxxxxxxxxxx", InstName.Fnmadd_S, InstEmit.Fnmadd_S, OpCodeSimdReg.Create); + SetA64("000111110x1xxxxx1xxxxxxxxxxxxxxx", InstName.Fnmsub_S, InstEmit.Fnmsub_S, OpCodeSimdReg.Create); + SetA64("000111100x1xxxxx100010xxxxxxxxxx", InstName.Fnmul_S, InstEmit.Fnmul_S, OpCodeSimdReg.Create); + SetA64("010111101x100001110110xxxxxxxxxx", InstName.Frecpe_S, InstEmit.Frecpe_S, OpCodeSimd.Create); + SetA64("0>0011101<100001110110xxxxxxxxxx", InstName.Frecpe_V, InstEmit.Frecpe_V, OpCodeSimd.Create); + SetA64("010111100x1xxxxx111111xxxxxxxxxx", InstName.Frecps_S, InstEmit.Frecps_S, OpCodeSimdReg.Create); + SetA64("0>0011100<1xxxxx111111xxxxxxxxxx", InstName.Frecps_V, InstEmit.Frecps_V, OpCodeSimdReg.Create); + SetA64("010111101x100001111110xxxxxxxxxx", InstName.Frecpx_S, InstEmit.Frecpx_S, OpCodeSimd.Create); + SetA64("000111100x100110010000xxxxxxxxxx", InstName.Frinta_S, InstEmit.Frinta_S, OpCodeSimd.Create); + SetA64("0>1011100<100001100010xxxxxxxxxx", InstName.Frinta_V, InstEmit.Frinta_V, OpCodeSimd.Create); + SetA64("000111100x100111110000xxxxxxxxxx", InstName.Frinti_S, InstEmit.Frinti_S, OpCodeSimd.Create); + SetA64("0>1011101<100001100110xxxxxxxxxx", InstName.Frinti_V, InstEmit.Frinti_V, OpCodeSimd.Create); + SetA64("000111100x100101010000xxxxxxxxxx", InstName.Frintm_S, InstEmit.Frintm_S, OpCodeSimd.Create); + SetA64("0>0011100<100001100110xxxxxxxxxx", InstName.Frintm_V, InstEmit.Frintm_V, OpCodeSimd.Create); + SetA64("000111100x100100010000xxxxxxxxxx", InstName.Frintn_S, InstEmit.Frintn_S, OpCodeSimd.Create); + SetA64("0>0011100<100001100010xxxxxxxxxx", InstName.Frintn_V, InstEmit.Frintn_V, OpCodeSimd.Create); + SetA64("000111100x100100110000xxxxxxxxxx", InstName.Frintp_S, InstEmit.Frintp_S, OpCodeSimd.Create); + SetA64("0>0011101<100001100010xxxxxxxxxx", InstName.Frintp_V, InstEmit.Frintp_V, OpCodeSimd.Create); + SetA64("000111100x100111010000xxxxxxxxxx", InstName.Frintx_S, InstEmit.Frintx_S, OpCodeSimd.Create); + SetA64("0>1011100<100001100110xxxxxxxxxx", InstName.Frintx_V, InstEmit.Frintx_V, OpCodeSimd.Create); + SetA64("000111100x100101110000xxxxxxxxxx", InstName.Frintz_S, InstEmit.Frintz_S, OpCodeSimd.Create); + SetA64("0>0011101<100001100110xxxxxxxxxx", InstName.Frintz_V, InstEmit.Frintz_V, OpCodeSimd.Create); + SetA64("011111101x100001110110xxxxxxxxxx", InstName.Frsqrte_S, InstEmit.Frsqrte_S, OpCodeSimd.Create); + SetA64("0>1011101<100001110110xxxxxxxxxx", InstName.Frsqrte_V, InstEmit.Frsqrte_V, OpCodeSimd.Create); + SetA64("010111101x1xxxxx111111xxxxxxxxxx", InstName.Frsqrts_S, InstEmit.Frsqrts_S, OpCodeSimdReg.Create); + SetA64("0>0011101<1xxxxx111111xxxxxxxxxx", InstName.Frsqrts_V, InstEmit.Frsqrts_V, OpCodeSimdReg.Create); + SetA64("000111100x100001110000xxxxxxxxxx", InstName.Fsqrt_S, InstEmit.Fsqrt_S, OpCodeSimd.Create); + SetA64("0>1011101<100001111110xxxxxxxxxx", InstName.Fsqrt_V, InstEmit.Fsqrt_V, OpCodeSimd.Create); + SetA64("000111100x1xxxxx001110xxxxxxxxxx", InstName.Fsub_S, InstEmit.Fsub_S, OpCodeSimdReg.Create); + SetA64("0>0011101<1xxxxx110101xxxxxxxxxx", InstName.Fsub_V, InstEmit.Fsub_V, OpCodeSimdReg.Create); + SetA64("01001110000xxxxx000111xxxxxxxxxx", InstName.Ins_Gp, InstEmit.Ins_Gp, OpCodeSimdIns.Create); + SetA64("01101110000xxxxx0xxxx1xxxxxxxxxx", InstName.Ins_V, InstEmit.Ins_V, OpCodeSimdIns.Create); + SetA64("0x00110001000000xxxxxxxxxxxxxxxx", InstName.Ld__Vms, InstEmit.Ld__Vms, OpCodeSimdMemMs.Create); + SetA64("0x001100110xxxxxxxxxxxxxxxxxxxxx", InstName.Ld__Vms, InstEmit.Ld__Vms, OpCodeSimdMemMs.Create); + SetA64("0x00110101x00000xxxxxxxxxxxxxxxx", InstName.Ld__Vss, InstEmit.Ld__Vss, OpCodeSimdMemSs.Create); + SetA64("0x00110111xxxxxxxxxxxxxxxxxxxxxx", InstName.Ld__Vss, InstEmit.Ld__Vss, OpCodeSimdMemSs.Create); + SetA64("<<10110xx1xxxxxxxxxxxxxxxxxxxxxx", InstName.Ldp, InstEmit.Ldp, OpCodeSimdMemPair.Create); + SetA64("xx111100x10xxxxxxxxx00xxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeSimdMemImm.Create); + SetA64("xx111100x10xxxxxxxxx01xxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeSimdMemImm.Create); + SetA64("xx111100x10xxxxxxxxx11xxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeSimdMemImm.Create); + SetA64("xx111101x1xxxxxxxxxxxxxxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeSimdMemImm.Create); + SetA64("xx111100x11xxxxxx1xx10xxxxxxxxxx", InstName.Ldr, InstEmit.Ldr, OpCodeSimdMemReg.Create); + SetA64("xx011100xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Ldr_Literal, InstEmit.Ldr_Literal, OpCodeSimdMemLit.Create); + SetA64("0x001110<<1xxxxx100101xxxxxxxxxx", InstName.Mla_V, InstEmit.Mla_V, OpCodeSimdReg.Create); + SetA64("0x101111xxxxxxxx0000x0xxxxxxxxxx", InstName.Mla_Ve, InstEmit.Mla_Ve, OpCodeSimdRegElem.Create); + SetA64("0x101110<<1xxxxx100101xxxxxxxxxx", InstName.Mls_V, InstEmit.Mls_V, OpCodeSimdReg.Create); + SetA64("0x101111xxxxxxxx0100x0xxxxxxxxxx", InstName.Mls_Ve, InstEmit.Mls_Ve, OpCodeSimdRegElem.Create); + SetA64("0x00111100000xxx0xx001xxxxxxxxxx", InstName.Movi_V, InstEmit.Movi_V, OpCodeSimdImm.Create); + SetA64("0x00111100000xxx10x001xxxxxxxxxx", InstName.Movi_V, InstEmit.Movi_V, OpCodeSimdImm.Create); + SetA64("0x00111100000xxx110x01xxxxxxxxxx", InstName.Movi_V, InstEmit.Movi_V, OpCodeSimdImm.Create); + SetA64("0xx0111100000xxx111001xxxxxxxxxx", InstName.Movi_V, InstEmit.Movi_V, OpCodeSimdImm.Create); + SetA64("0x001110<<1xxxxx100111xxxxxxxxxx", InstName.Mul_V, InstEmit.Mul_V, OpCodeSimdReg.Create); + SetA64("0x001111xxxxxxxx1000x0xxxxxxxxxx", InstName.Mul_Ve, InstEmit.Mul_Ve, OpCodeSimdRegElem.Create); + SetA64("0x10111100000xxx0xx001xxxxxxxxxx", InstName.Mvni_V, InstEmit.Mvni_V, OpCodeSimdImm.Create); + SetA64("0x10111100000xxx10x001xxxxxxxxxx", InstName.Mvni_V, InstEmit.Mvni_V, OpCodeSimdImm.Create); + SetA64("0x10111100000xxx110x01xxxxxxxxxx", InstName.Mvni_V, InstEmit.Mvni_V, OpCodeSimdImm.Create); + SetA64("0111111011100000101110xxxxxxxxxx", InstName.Neg_S, InstEmit.Neg_S, OpCodeSimd.Create); + SetA64("0>101110<<100000101110xxxxxxxxxx", InstName.Neg_V, InstEmit.Neg_V, OpCodeSimd.Create); + SetA64("0x10111000100000010110xxxxxxxxxx", InstName.Not_V, InstEmit.Not_V, OpCodeSimd.Create); + SetA64("0x001110111xxxxx000111xxxxxxxxxx", InstName.Orn_V, InstEmit.Orn_V, OpCodeSimdReg.Create); + SetA64("0x001110101xxxxx000111xxxxxxxxxx", InstName.Orr_V, InstEmit.Orr_V, OpCodeSimdReg.Create); + SetA64("0x00111100000xxx0xx101xxxxxxxxxx", InstName.Orr_Vi, InstEmit.Orr_Vi, OpCodeSimdImm.Create); + SetA64("0x00111100000xxx10x101xxxxxxxxxx", InstName.Orr_Vi, InstEmit.Orr_Vi, OpCodeSimdImm.Create); + SetA64("0x001110001xxxxx111000xxxxxxxxxx", InstName.Pmull_V, InstEmit.Pmull_V, OpCodeSimdReg.Create); + SetA64("0x001110111xxxxx111000xxxxxxxxxx", InstName.Pmull_V, InstEmit.Pmull_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx010000xxxxxxxxxx", InstName.Raddhn_V, InstEmit.Raddhn_V, OpCodeSimdReg.Create); + SetA64("0x10111001100000010110xxxxxxxxxx", InstName.Rbit_V, InstEmit.Rbit_V, OpCodeSimd.Create); + SetA64("0x00111000100000000110xxxxxxxxxx", InstName.Rev16_V, InstEmit.Rev16_V, OpCodeSimd.Create); + SetA64("0x1011100x100000000010xxxxxxxxxx", InstName.Rev32_V, InstEmit.Rev32_V, OpCodeSimd.Create); + SetA64("0x001110<<100000000010xxxxxxxxxx", InstName.Rev64_V, InstEmit.Rev64_V, OpCodeSimd.Create); + SetA64("0x00111100>>>xxx100011xxxxxxxxxx", InstName.Rshrn_V, InstEmit.Rshrn_V, OpCodeSimdShImm.Create); + SetA64("0x101110<<1xxxxx011000xxxxxxxxxx", InstName.Rsubhn_V, InstEmit.Rsubhn_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx011111xxxxxxxxxx", InstName.Saba_V, InstEmit.Saba_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx010100xxxxxxxxxx", InstName.Sabal_V, InstEmit.Sabal_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx011101xxxxxxxxxx", InstName.Sabd_V, InstEmit.Sabd_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx011100xxxxxxxxxx", InstName.Sabdl_V, InstEmit.Sabdl_V, OpCodeSimdReg.Create); + SetA64("0x001110<<100000011010xxxxxxxxxx", InstName.Sadalp_V, InstEmit.Sadalp_V, OpCodeSimd.Create); + SetA64("0x001110<<1xxxxx000000xxxxxxxxxx", InstName.Saddl_V, InstEmit.Saddl_V, OpCodeSimdReg.Create); + SetA64("0x001110<<100000001010xxxxxxxxxx", InstName.Saddlp_V, InstEmit.Saddlp_V, OpCodeSimd.Create); + SetA64("000011100x110000001110xxxxxxxxxx", InstName.Saddlv_V, InstEmit.Saddlv_V, OpCodeSimd.Create); + SetA64("01001110<<110000001110xxxxxxxxxx", InstName.Saddlv_V, InstEmit.Saddlv_V, OpCodeSimd.Create); + SetA64("0x001110<<1xxxxx000100xxxxxxxxxx", InstName.Saddw_V, InstEmit.Saddw_V, OpCodeSimdReg.Create); + SetA64("x00111100x100010000000xxxxxxxxxx", InstName.Scvtf_Gp, InstEmit.Scvtf_Gp, OpCodeSimdCvt.Create); + SetA64(">00111100x000010>xxxxxxxxxxxxxxx", InstName.Scvtf_Gp_Fixed, InstEmit.Scvtf_Gp_Fixed, OpCodeSimdCvt.Create); + SetA64("010111100x100001110110xxxxxxxxxx", InstName.Scvtf_S, InstEmit.Scvtf_S, OpCodeSimd.Create); + SetA64("010111110>>xxxxx111001xxxxxxxxxx", InstName.Scvtf_S_Fixed, InstEmit.Scvtf_S_Fixed, OpCodeSimdShImm.Create); + SetA64("0>0011100<100001110110xxxxxxxxxx", InstName.Scvtf_V, InstEmit.Scvtf_V, OpCodeSimd.Create); + SetA64("0x001111001xxxxx111001xxxxxxxxxx", InstName.Scvtf_V_Fixed, InstEmit.Scvtf_V_Fixed, OpCodeSimdShImm.Create); + SetA64("0100111101xxxxxx111001xxxxxxxxxx", InstName.Scvtf_V_Fixed, InstEmit.Scvtf_V_Fixed, OpCodeSimdShImm.Create); + SetA64("01011110000xxxxx000000xxxxxxxxxx", InstName.Sha1c_V, InstEmit.Sha1c_V, OpCodeSimdReg.Create); + SetA64("0101111000101000000010xxxxxxxxxx", InstName.Sha1h_V, InstEmit.Sha1h_V, OpCodeSimd.Create); + SetA64("01011110000xxxxx001000xxxxxxxxxx", InstName.Sha1m_V, InstEmit.Sha1m_V, OpCodeSimdReg.Create); + SetA64("01011110000xxxxx000100xxxxxxxxxx", InstName.Sha1p_V, InstEmit.Sha1p_V, OpCodeSimdReg.Create); + SetA64("01011110000xxxxx001100xxxxxxxxxx", InstName.Sha1su0_V, InstEmit.Sha1su0_V, OpCodeSimdReg.Create); + SetA64("0101111000101000000110xxxxxxxxxx", InstName.Sha1su1_V, InstEmit.Sha1su1_V, OpCodeSimd.Create); + SetA64("01011110000xxxxx010000xxxxxxxxxx", InstName.Sha256h_V, InstEmit.Sha256h_V, OpCodeSimdReg.Create); + SetA64("01011110000xxxxx010100xxxxxxxxxx", InstName.Sha256h2_V, InstEmit.Sha256h2_V, OpCodeSimdReg.Create); + SetA64("0101111000101000001010xxxxxxxxxx", InstName.Sha256su0_V, InstEmit.Sha256su0_V, OpCodeSimd.Create); + SetA64("01011110000xxxxx011000xxxxxxxxxx", InstName.Sha256su1_V, InstEmit.Sha256su1_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx000001xxxxxxxxxx", InstName.Shadd_V, InstEmit.Shadd_V, OpCodeSimdReg.Create); + SetA64("0101111101xxxxxx010101xxxxxxxxxx", InstName.Shl_S, InstEmit.Shl_S, OpCodeSimdShImm.Create); + SetA64("0x00111100>>>xxx010101xxxxxxxxxx", InstName.Shl_V, InstEmit.Shl_V, OpCodeSimdShImm.Create); + SetA64("0100111101xxxxxx010101xxxxxxxxxx", InstName.Shl_V, InstEmit.Shl_V, OpCodeSimdShImm.Create); + SetA64("0x101110<<100001001110xxxxxxxxxx", InstName.Shll_V, InstEmit.Shll_V, OpCodeSimd.Create); + SetA64("0x00111100>>>xxx100001xxxxxxxxxx", InstName.Shrn_V, InstEmit.Shrn_V, OpCodeSimdShImm.Create); + SetA64("0x001110<<1xxxxx001001xxxxxxxxxx", InstName.Shsub_V, InstEmit.Shsub_V, OpCodeSimdReg.Create); + SetA64("0111111101xxxxxx010101xxxxxxxxxx", InstName.Sli_S, InstEmit.Sli_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx010101xxxxxxxxxx", InstName.Sli_V, InstEmit.Sli_V, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx010101xxxxxxxxxx", InstName.Sli_V, InstEmit.Sli_V, OpCodeSimdShImm.Create); + SetA64("0x001110<<1xxxxx011001xxxxxxxxxx", InstName.Smax_V, InstEmit.Smax_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx101001xxxxxxxxxx", InstName.Smaxp_V, InstEmit.Smaxp_V, OpCodeSimdReg.Create); + SetA64("000011100x110000101010xxxxxxxxxx", InstName.Smaxv_V, InstEmit.Smaxv_V, OpCodeSimd.Create); + SetA64("01001110<<110000101010xxxxxxxxxx", InstName.Smaxv_V, InstEmit.Smaxv_V, OpCodeSimd.Create); + SetA64("0x001110<<1xxxxx011011xxxxxxxxxx", InstName.Smin_V, InstEmit.Smin_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx101011xxxxxxxxxx", InstName.Sminp_V, InstEmit.Sminp_V, OpCodeSimdReg.Create); + SetA64("000011100x110001101010xxxxxxxxxx", InstName.Sminv_V, InstEmit.Sminv_V, OpCodeSimd.Create); + SetA64("01001110<<110001101010xxxxxxxxxx", InstName.Sminv_V, InstEmit.Sminv_V, OpCodeSimd.Create); + SetA64("0x001110<<1xxxxx100000xxxxxxxxxx", InstName.Smlal_V, InstEmit.Smlal_V, OpCodeSimdReg.Create); + SetA64("0x001111xxxxxxxx0010x0xxxxxxxxxx", InstName.Smlal_Ve, InstEmit.Smlal_Ve, OpCodeSimdRegElem.Create); + SetA64("0x001110<<1xxxxx101000xxxxxxxxxx", InstName.Smlsl_V, InstEmit.Smlsl_V, OpCodeSimdReg.Create); + SetA64("0x001111xxxxxxxx0110x0xxxxxxxxxx", InstName.Smlsl_Ve, InstEmit.Smlsl_Ve, OpCodeSimdRegElem.Create); + SetA64("0x001110000xxxxx001011xxxxxxxxxx", InstName.Smov_S, InstEmit.Smov_S, OpCodeSimdIns.Create); + SetA64("0x001110<<1xxxxx110000xxxxxxxxxx", InstName.Smull_V, InstEmit.Smull_V, OpCodeSimdReg.Create); + SetA64("0x001111xxxxxxxx1010x0xxxxxxxxxx", InstName.Smull_Ve, InstEmit.Smull_Ve, OpCodeSimdRegElem.Create); + SetA64("01011110xx100000011110xxxxxxxxxx", InstName.Sqabs_S, InstEmit.Sqabs_S, OpCodeSimd.Create); + SetA64("0>001110<<100000011110xxxxxxxxxx", InstName.Sqabs_V, InstEmit.Sqabs_V, OpCodeSimd.Create); + SetA64("01011110xx1xxxxx000011xxxxxxxxxx", InstName.Sqadd_S, InstEmit.Sqadd_S, OpCodeSimdReg.Create); + SetA64("0>001110<<1xxxxx000011xxxxxxxxxx", InstName.Sqadd_V, InstEmit.Sqadd_V, OpCodeSimdReg.Create); + SetA64("01011110011xxxxx101101xxxxxxxxxx", InstName.Sqdmulh_S, InstEmit.Sqdmulh_S, OpCodeSimdReg.Create); + SetA64("01011110101xxxxx101101xxxxxxxxxx", InstName.Sqdmulh_S, InstEmit.Sqdmulh_S, OpCodeSimdReg.Create); + SetA64("0x001110011xxxxx101101xxxxxxxxxx", InstName.Sqdmulh_V, InstEmit.Sqdmulh_V, OpCodeSimdReg.Create); + SetA64("0x001110101xxxxx101101xxxxxxxxxx", InstName.Sqdmulh_V, InstEmit.Sqdmulh_V, OpCodeSimdReg.Create); + SetA64("0x00111101xxxxxx1100x0xxxxxxxxxx", InstName.Sqdmulh_Ve, InstEmit.Sqdmulh_Ve, OpCodeSimdRegElem.Create); + SetA64("0x00111110xxxxxx1100x0xxxxxxxxxx", InstName.Sqdmulh_Ve, InstEmit.Sqdmulh_Ve, OpCodeSimdRegElem.Create); + SetA64("01111110xx100000011110xxxxxxxxxx", InstName.Sqneg_S, InstEmit.Sqneg_S, OpCodeSimd.Create); + SetA64("0>101110<<100000011110xxxxxxxxxx", InstName.Sqneg_V, InstEmit.Sqneg_V, OpCodeSimd.Create); + SetA64("01111110011xxxxx101101xxxxxxxxxx", InstName.Sqrdmulh_S, InstEmit.Sqrdmulh_S, OpCodeSimdReg.Create); + SetA64("01111110101xxxxx101101xxxxxxxxxx", InstName.Sqrdmulh_S, InstEmit.Sqrdmulh_S, OpCodeSimdReg.Create); + SetA64("0x101110011xxxxx101101xxxxxxxxxx", InstName.Sqrdmulh_V, InstEmit.Sqrdmulh_V, OpCodeSimdReg.Create); + SetA64("0x101110101xxxxx101101xxxxxxxxxx", InstName.Sqrdmulh_V, InstEmit.Sqrdmulh_V, OpCodeSimdReg.Create); + SetA64("0x00111101xxxxxx1101x0xxxxxxxxxx", InstName.Sqrdmulh_Ve, InstEmit.Sqrdmulh_Ve, OpCodeSimdRegElem.Create); + SetA64("0x00111110xxxxxx1101x0xxxxxxxxxx", InstName.Sqrdmulh_Ve, InstEmit.Sqrdmulh_Ve, OpCodeSimdRegElem.Create); + SetA64("0>001110<<1xxxxx010111xxxxxxxxxx", InstName.Sqrshl_V, InstEmit.Sqrshl_V, OpCodeSimdReg.Create); + SetA64("0101111100>>>xxx100111xxxxxxxxxx", InstName.Sqrshrn_S, InstEmit.Sqrshrn_S, OpCodeSimdShImm.Create); + SetA64("0x00111100>>>xxx100111xxxxxxxxxx", InstName.Sqrshrn_V, InstEmit.Sqrshrn_V, OpCodeSimdShImm.Create); + SetA64("0111111100>>>xxx100011xxxxxxxxxx", InstName.Sqrshrun_S, InstEmit.Sqrshrun_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx100011xxxxxxxxxx", InstName.Sqrshrun_V, InstEmit.Sqrshrun_V, OpCodeSimdShImm.Create); + SetA64("0>001110<<1xxxxx010011xxxxxxxxxx", InstName.Sqshl_V, InstEmit.Sqshl_V, OpCodeSimdReg.Create); + SetA64("0101111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_S, InstEmit.Sqshrn_S, OpCodeSimdShImm.Create); + SetA64("0x00111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_V, InstEmit.Sqshrn_V, OpCodeSimdShImm.Create); + SetA64("0111111100>>>xxx100001xxxxxxxxxx", InstName.Sqshrun_S, InstEmit.Sqshrun_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx100001xxxxxxxxxx", InstName.Sqshrun_V, InstEmit.Sqshrun_V, OpCodeSimdShImm.Create); + SetA64("01011110xx1xxxxx001011xxxxxxxxxx", InstName.Sqsub_S, InstEmit.Sqsub_S, OpCodeSimdReg.Create); + SetA64("0>001110<<1xxxxx001011xxxxxxxxxx", InstName.Sqsub_V, InstEmit.Sqsub_V, OpCodeSimdReg.Create); + SetA64("01011110<<100001010010xxxxxxxxxx", InstName.Sqxtn_S, InstEmit.Sqxtn_S, OpCodeSimd.Create); + SetA64("0x001110<<100001010010xxxxxxxxxx", InstName.Sqxtn_V, InstEmit.Sqxtn_V, OpCodeSimd.Create); + SetA64("01111110<<100001001010xxxxxxxxxx", InstName.Sqxtun_S, InstEmit.Sqxtun_S, OpCodeSimd.Create); + SetA64("0x101110<<100001001010xxxxxxxxxx", InstName.Sqxtun_V, InstEmit.Sqxtun_V, OpCodeSimd.Create); + SetA64("0x001110<<1xxxxx000101xxxxxxxxxx", InstName.Srhadd_V, InstEmit.Srhadd_V, OpCodeSimdReg.Create); + SetA64("0111111101xxxxxx010001xxxxxxxxxx", InstName.Sri_S, InstEmit.Sri_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx010001xxxxxxxxxx", InstName.Sri_V, InstEmit.Sri_V, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx010001xxxxxxxxxx", InstName.Sri_V, InstEmit.Sri_V, OpCodeSimdShImm.Create); + SetA64("0>001110<<1xxxxx010101xxxxxxxxxx", InstName.Srshl_V, InstEmit.Srshl_V, OpCodeSimdReg.Create); + SetA64("0101111101xxxxxx001001xxxxxxxxxx", InstName.Srshr_S, InstEmit.Srshr_S, OpCodeSimdShImm.Create); + SetA64("0x00111100>>>xxx001001xxxxxxxxxx", InstName.Srshr_V, InstEmit.Srshr_V, OpCodeSimdShImm.Create); + SetA64("0100111101xxxxxx001001xxxxxxxxxx", InstName.Srshr_V, InstEmit.Srshr_V, OpCodeSimdShImm.Create); + SetA64("0101111101xxxxxx001101xxxxxxxxxx", InstName.Srsra_S, InstEmit.Srsra_S, OpCodeSimdShImm.Create); + SetA64("0x00111100>>>xxx001101xxxxxxxxxx", InstName.Srsra_V, InstEmit.Srsra_V, OpCodeSimdShImm.Create); + SetA64("0100111101xxxxxx001101xxxxxxxxxx", InstName.Srsra_V, InstEmit.Srsra_V, OpCodeSimdShImm.Create); + SetA64("01011110111xxxxx010001xxxxxxxxxx", InstName.Sshl_S, InstEmit.Sshl_S, OpCodeSimdReg.Create); + SetA64("0>001110<<1xxxxx010001xxxxxxxxxx", InstName.Sshl_V, InstEmit.Sshl_V, OpCodeSimdReg.Create); + SetA64("0x00111100>>>xxx101001xxxxxxxxxx", InstName.Sshll_V, InstEmit.Sshll_V, OpCodeSimdShImm.Create); + SetA64("0101111101xxxxxx000001xxxxxxxxxx", InstName.Sshr_S, InstEmit.Sshr_S, OpCodeSimdShImm.Create); + SetA64("0x00111100>>>xxx000001xxxxxxxxxx", InstName.Sshr_V, InstEmit.Sshr_V, OpCodeSimdShImm.Create); + SetA64("0100111101xxxxxx000001xxxxxxxxxx", InstName.Sshr_V, InstEmit.Sshr_V, OpCodeSimdShImm.Create); + SetA64("0101111101xxxxxx000101xxxxxxxxxx", InstName.Ssra_S, InstEmit.Ssra_S, OpCodeSimdShImm.Create); + SetA64("0x00111100>>>xxx000101xxxxxxxxxx", InstName.Ssra_V, InstEmit.Ssra_V, OpCodeSimdShImm.Create); + SetA64("0100111101xxxxxx000101xxxxxxxxxx", InstName.Ssra_V, InstEmit.Ssra_V, OpCodeSimdShImm.Create); + SetA64("0x001110<<1xxxxx001000xxxxxxxxxx", InstName.Ssubl_V, InstEmit.Ssubl_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx001100xxxxxxxxxx", InstName.Ssubw_V, InstEmit.Ssubw_V, OpCodeSimdReg.Create); + SetA64("0x00110000000000xxxxxxxxxxxxxxxx", InstName.St__Vms, InstEmit.St__Vms, OpCodeSimdMemMs.Create); + SetA64("0x001100100xxxxxxxxxxxxxxxxxxxxx", InstName.St__Vms, InstEmit.St__Vms, OpCodeSimdMemMs.Create); + SetA64("0x00110100x00000xxxxxxxxxxxxxxxx", InstName.St__Vss, InstEmit.St__Vss, OpCodeSimdMemSs.Create); + SetA64("0x00110110xxxxxxxxxxxxxxxxxxxxxx", InstName.St__Vss, InstEmit.St__Vss, OpCodeSimdMemSs.Create); + SetA64("<<10110xx0xxxxxxxxxxxxxxxxxxxxxx", InstName.Stp, InstEmit.Stp, OpCodeSimdMemPair.Create); + SetA64("xx111100x00xxxxxxxxx00xxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeSimdMemImm.Create); + SetA64("xx111100x00xxxxxxxxx01xxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeSimdMemImm.Create); + SetA64("xx111100x00xxxxxxxxx11xxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeSimdMemImm.Create); + SetA64("xx111101x0xxxxxxxxxxxxxxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeSimdMemImm.Create); + SetA64("xx111100x01xxxxxx1xx10xxxxxxxxxx", InstName.Str, InstEmit.Str, OpCodeSimdMemReg.Create); + SetA64("01111110111xxxxx100001xxxxxxxxxx", InstName.Sub_S, InstEmit.Sub_S, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx100001xxxxxxxxxx", InstName.Sub_V, InstEmit.Sub_V, OpCodeSimdReg.Create); + SetA64("0x001110<<1xxxxx011000xxxxxxxxxx", InstName.Subhn_V, InstEmit.Subhn_V, OpCodeSimdReg.Create); + SetA64("01011110xx100000001110xxxxxxxxxx", InstName.Suqadd_S, InstEmit.Suqadd_S, OpCodeSimd.Create); + SetA64("0>001110<<100000001110xxxxxxxxxx", InstName.Suqadd_V, InstEmit.Suqadd_V, OpCodeSimd.Create); + SetA64("0x001110000xxxxx0xx000xxxxxxxxxx", InstName.Tbl_V, InstEmit.Tbl_V, OpCodeSimdTbl.Create); + SetA64("0x001110000xxxxx0xx100xxxxxxxxxx", InstName.Tbx_V, InstEmit.Tbx_V, OpCodeSimdTbl.Create); + SetA64("0>001110<<0xxxxx001010xxxxxxxxxx", InstName.Trn1_V, InstEmit.Trn1_V, OpCodeSimdReg.Create); + SetA64("0>001110<<0xxxxx011010xxxxxxxxxx", InstName.Trn2_V, InstEmit.Trn2_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx011111xxxxxxxxxx", InstName.Uaba_V, InstEmit.Uaba_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx010100xxxxxxxxxx", InstName.Uabal_V, InstEmit.Uabal_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx011101xxxxxxxxxx", InstName.Uabd_V, InstEmit.Uabd_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx011100xxxxxxxxxx", InstName.Uabdl_V, InstEmit.Uabdl_V, OpCodeSimdReg.Create); + SetA64("0x101110<<100000011010xxxxxxxxxx", InstName.Uadalp_V, InstEmit.Uadalp_V, OpCodeSimd.Create); + SetA64("0x101110<<1xxxxx000000xxxxxxxxxx", InstName.Uaddl_V, InstEmit.Uaddl_V, OpCodeSimdReg.Create); + SetA64("0x101110<<100000001010xxxxxxxxxx", InstName.Uaddlp_V, InstEmit.Uaddlp_V, OpCodeSimd.Create); + SetA64("001011100x110000001110xxxxxxxxxx", InstName.Uaddlv_V, InstEmit.Uaddlv_V, OpCodeSimd.Create); + SetA64("01101110<<110000001110xxxxxxxxxx", InstName.Uaddlv_V, InstEmit.Uaddlv_V, OpCodeSimd.Create); + SetA64("0x101110<<1xxxxx000100xxxxxxxxxx", InstName.Uaddw_V, InstEmit.Uaddw_V, OpCodeSimdReg.Create); + SetA64("x00111100x100011000000xxxxxxxxxx", InstName.Ucvtf_Gp, InstEmit.Ucvtf_Gp, OpCodeSimdCvt.Create); + SetA64(">00111100x000011>xxxxxxxxxxxxxxx", InstName.Ucvtf_Gp_Fixed, InstEmit.Ucvtf_Gp_Fixed, OpCodeSimdCvt.Create); + SetA64("011111100x100001110110xxxxxxxxxx", InstName.Ucvtf_S, InstEmit.Ucvtf_S, OpCodeSimd.Create); + SetA64("011111110>>xxxxx111001xxxxxxxxxx", InstName.Ucvtf_S_Fixed, InstEmit.Ucvtf_S_Fixed, OpCodeSimdShImm.Create); + SetA64("0>1011100<100001110110xxxxxxxxxx", InstName.Ucvtf_V, InstEmit.Ucvtf_V, OpCodeSimd.Create); + SetA64("0x101111001xxxxx111001xxxxxxxxxx", InstName.Ucvtf_V_Fixed, InstEmit.Ucvtf_V_Fixed, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx111001xxxxxxxxxx", InstName.Ucvtf_V_Fixed, InstEmit.Ucvtf_V_Fixed, OpCodeSimdShImm.Create); + SetA64("0x101110<<1xxxxx000001xxxxxxxxxx", InstName.Uhadd_V, InstEmit.Uhadd_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx001001xxxxxxxxxx", InstName.Uhsub_V, InstEmit.Uhsub_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx011001xxxxxxxxxx", InstName.Umax_V, InstEmit.Umax_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx101001xxxxxxxxxx", InstName.Umaxp_V, InstEmit.Umaxp_V, OpCodeSimdReg.Create); + SetA64("001011100x110000101010xxxxxxxxxx", InstName.Umaxv_V, InstEmit.Umaxv_V, OpCodeSimd.Create); + SetA64("01101110<<110000101010xxxxxxxxxx", InstName.Umaxv_V, InstEmit.Umaxv_V, OpCodeSimd.Create); + SetA64("0x101110<<1xxxxx011011xxxxxxxxxx", InstName.Umin_V, InstEmit.Umin_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx101011xxxxxxxxxx", InstName.Uminp_V, InstEmit.Uminp_V, OpCodeSimdReg.Create); + SetA64("001011100x110001101010xxxxxxxxxx", InstName.Uminv_V, InstEmit.Uminv_V, OpCodeSimd.Create); + SetA64("01101110<<110001101010xxxxxxxxxx", InstName.Uminv_V, InstEmit.Uminv_V, OpCodeSimd.Create); + SetA64("0x101110<<1xxxxx100000xxxxxxxxxx", InstName.Umlal_V, InstEmit.Umlal_V, OpCodeSimdReg.Create); + SetA64("0x101111xxxxxxxx0010x0xxxxxxxxxx", InstName.Umlal_Ve, InstEmit.Umlal_Ve, OpCodeSimdRegElem.Create); + SetA64("0x101110<<1xxxxx101000xxxxxxxxxx", InstName.Umlsl_V, InstEmit.Umlsl_V, OpCodeSimdReg.Create); + SetA64("0x101111xxxxxxxx0110x0xxxxxxxxxx", InstName.Umlsl_Ve, InstEmit.Umlsl_Ve, OpCodeSimdRegElem.Create); + SetA64("0x001110000xxxxx001111xxxxxxxxxx", InstName.Umov_S, InstEmit.Umov_S, OpCodeSimdIns.Create); + SetA64("0x101110<<1xxxxx110000xxxxxxxxxx", InstName.Umull_V, InstEmit.Umull_V, OpCodeSimdReg.Create); + SetA64("0x101111xxxxxxxx1010x0xxxxxxxxxx", InstName.Umull_Ve, InstEmit.Umull_Ve, OpCodeSimdRegElem.Create); + SetA64("01111110xx1xxxxx000011xxxxxxxxxx", InstName.Uqadd_S, InstEmit.Uqadd_S, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx000011xxxxxxxxxx", InstName.Uqadd_V, InstEmit.Uqadd_V, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx010111xxxxxxxxxx", InstName.Uqrshl_V, InstEmit.Uqrshl_V, OpCodeSimdReg.Create); + SetA64("0111111100>>>xxx100111xxxxxxxxxx", InstName.Uqrshrn_S, InstEmit.Uqrshrn_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx100111xxxxxxxxxx", InstName.Uqrshrn_V, InstEmit.Uqrshrn_V, OpCodeSimdShImm.Create); + SetA64("0>101110<<1xxxxx010011xxxxxxxxxx", InstName.Uqshl_V, InstEmit.Uqshl_V, OpCodeSimdReg.Create); + SetA64("0111111100>>>xxx100101xxxxxxxxxx", InstName.Uqshrn_S, InstEmit.Uqshrn_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx100101xxxxxxxxxx", InstName.Uqshrn_V, InstEmit.Uqshrn_V, OpCodeSimdShImm.Create); + SetA64("01111110xx1xxxxx001011xxxxxxxxxx", InstName.Uqsub_S, InstEmit.Uqsub_S, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx001011xxxxxxxxxx", InstName.Uqsub_V, InstEmit.Uqsub_V, OpCodeSimdReg.Create); + SetA64("01111110<<100001010010xxxxxxxxxx", InstName.Uqxtn_S, InstEmit.Uqxtn_S, OpCodeSimd.Create); + SetA64("0x101110<<100001010010xxxxxxxxxx", InstName.Uqxtn_V, InstEmit.Uqxtn_V, OpCodeSimd.Create); + SetA64("0x101110<<1xxxxx000101xxxxxxxxxx", InstName.Urhadd_V, InstEmit.Urhadd_V, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx010101xxxxxxxxxx", InstName.Urshl_V, InstEmit.Urshl_V, OpCodeSimdReg.Create); + SetA64("0111111101xxxxxx001001xxxxxxxxxx", InstName.Urshr_S, InstEmit.Urshr_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx001001xxxxxxxxxx", InstName.Urshr_V, InstEmit.Urshr_V, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx001001xxxxxxxxxx", InstName.Urshr_V, InstEmit.Urshr_V, OpCodeSimdShImm.Create); + SetA64("0111111101xxxxxx001101xxxxxxxxxx", InstName.Ursra_S, InstEmit.Ursra_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx001101xxxxxxxxxx", InstName.Ursra_V, InstEmit.Ursra_V, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx001101xxxxxxxxxx", InstName.Ursra_V, InstEmit.Ursra_V, OpCodeSimdShImm.Create); + SetA64("01111110111xxxxx010001xxxxxxxxxx", InstName.Ushl_S, InstEmit.Ushl_S, OpCodeSimdReg.Create); + SetA64("0>101110<<1xxxxx010001xxxxxxxxxx", InstName.Ushl_V, InstEmit.Ushl_V, OpCodeSimdReg.Create); + SetA64("0x10111100>>>xxx101001xxxxxxxxxx", InstName.Ushll_V, InstEmit.Ushll_V, OpCodeSimdShImm.Create); + SetA64("0111111101xxxxxx000001xxxxxxxxxx", InstName.Ushr_S, InstEmit.Ushr_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx000001xxxxxxxxxx", InstName.Ushr_V, InstEmit.Ushr_V, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx000001xxxxxxxxxx", InstName.Ushr_V, InstEmit.Ushr_V, OpCodeSimdShImm.Create); + SetA64("01111110xx100000001110xxxxxxxxxx", InstName.Usqadd_S, InstEmit.Usqadd_S, OpCodeSimd.Create); + SetA64("0>101110<<100000001110xxxxxxxxxx", InstName.Usqadd_V, InstEmit.Usqadd_V, OpCodeSimd.Create); + SetA64("0111111101xxxxxx000101xxxxxxxxxx", InstName.Usra_S, InstEmit.Usra_S, OpCodeSimdShImm.Create); + SetA64("0x10111100>>>xxx000101xxxxxxxxxx", InstName.Usra_V, InstEmit.Usra_V, OpCodeSimdShImm.Create); + SetA64("0110111101xxxxxx000101xxxxxxxxxx", InstName.Usra_V, InstEmit.Usra_V, OpCodeSimdShImm.Create); + SetA64("0x101110<<1xxxxx001000xxxxxxxxxx", InstName.Usubl_V, InstEmit.Usubl_V, OpCodeSimdReg.Create); + SetA64("0x101110<<1xxxxx001100xxxxxxxxxx", InstName.Usubw_V, InstEmit.Usubw_V, OpCodeSimdReg.Create); + SetA64("0>001110<<0xxxxx000110xxxxxxxxxx", InstName.Uzp1_V, InstEmit.Uzp1_V, OpCodeSimdReg.Create); + SetA64("0>001110<<0xxxxx010110xxxxxxxxxx", InstName.Uzp2_V, InstEmit.Uzp2_V, OpCodeSimdReg.Create); + SetA64("0x001110<<100001001010xxxxxxxxxx", InstName.Xtn_V, InstEmit.Xtn_V, OpCodeSimd.Create); + SetA64("0>001110<<0xxxxx001110xxxxxxxxxx", InstName.Zip1_V, InstEmit.Zip1_V, OpCodeSimdReg.Create); + SetA64("0>001110<<0xxxxx011110xxxxxxxxxx", InstName.Zip2_V, InstEmit.Zip2_V, OpCodeSimdReg.Create); +#endregion + +#region "OpCode Table (AArch32, A32)" + // Base + SetA32("<<<<0010101xxxxxxxxxxxxxxxxxxxxx", InstName.Adc, InstEmit32.Adc, OpCode32AluImm.Create); + SetA32("<<<<0000101xxxxxxxxxxxxxxxx0xxxx", InstName.Adc, InstEmit32.Adc, OpCode32AluRsImm.Create); + SetA32("<<<<0000101xxxxxxxxxxxxx0xx1xxxx", InstName.Adc, InstEmit32.Adc, OpCode32AluRsReg.Create); + SetA32("<<<<0010100xxxxxxxxxxxxxxxxxxxxx", InstName.Add, InstEmit32.Add, OpCode32AluImm.Create); + SetA32("<<<<0000100xxxxxxxxxxxxxxxx0xxxx", InstName.Add, InstEmit32.Add, OpCode32AluRsImm.Create); + SetA32("<<<<0000100xxxxxxxxxxxxx0xx1xxxx", InstName.Add, InstEmit32.Add, OpCode32AluRsReg.Create); + SetA32("<<<<0010000xxxxxxxxxxxxxxxxxxxxx", InstName.And, InstEmit32.And, OpCode32AluImm.Create); + SetA32("<<<<0000000xxxxxxxxxxxxxxxx0xxxx", InstName.And, InstEmit32.And, OpCode32AluRsImm.Create); + SetA32("<<<<0000000xxxxxxxxxxxxx0xx1xxxx", InstName.And, InstEmit32.And, OpCode32AluRsReg.Create); + SetA32("<<<<1010xxxxxxxxxxxxxxxxxxxxxxxx", InstName.B, InstEmit32.B, OpCode32BImm.Create); + SetA32("<<<<0111110xxxxxxxxxxxxxx0011111", InstName.Bfc, InstEmit32.Bfc, OpCode32AluBf.Create); + SetA32("<<<<0111110xxxxxxxxxxxxxx001xxxx", InstName.Bfi, InstEmit32.Bfi, OpCode32AluBf.Create); + SetA32("<<<<0011110xxxxxxxxxxxxxxxxxxxxx", InstName.Bic, InstEmit32.Bic, OpCode32AluImm.Create); + SetA32("<<<<0001110xxxxxxxxxxxxxxxx0xxxx", InstName.Bic, InstEmit32.Bic, OpCode32AluRsImm.Create); + SetA32("<<<<0001110xxxxxxxxxxxxx0xx1xxxx", InstName.Bic, InstEmit32.Bic, OpCode32AluRsReg.Create); + SetA32("<<<<1011xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Bl, InstEmit32.Bl, OpCode32BImm.Create); + SetA32("1111101xxxxxxxxxxxxxxxxxxxxxxxxx", InstName.Blx, InstEmit32.Blx, OpCode32BImm.Create); + SetA32("<<<<000100101111111111110011xxxx", InstName.Blx, InstEmit32.Blxr, OpCode32BReg.Create); + SetA32("<<<<000100101111111111110001xxxx", InstName.Bx, InstEmit32.Bx, OpCode32BReg.Create); + SetA32("11110101011111111111000000011111", InstName.Clrex, InstEmit32.Clrex, OpCode32.Create); + SetA32("<<<<000101101111xxxx11110001xxxx", InstName.Clz, InstEmit32.Clz, OpCode32AluReg.Create); + SetA32("<<<<00110111xxxx0000xxxxxxxxxxxx", InstName.Cmn, InstEmit32.Cmn, OpCode32AluImm.Create); + SetA32("<<<<00010111xxxx0000xxxxxxx0xxxx", InstName.Cmn, InstEmit32.Cmn, OpCode32AluRsImm.Create); + SetA32("<<<<00010111xxxx0000xxxx0xx1xxxx", InstName.Cmn, InstEmit32.Cmn, OpCode32AluRsReg.Create); + SetA32("<<<<00110101xxxx0000xxxxxxxxxxxx", InstName.Cmp, InstEmit32.Cmp, OpCode32AluImm.Create); + SetA32("<<<<00010101xxxx0000xxxxxxx0xxxx", InstName.Cmp, InstEmit32.Cmp, OpCode32AluRsImm.Create); + SetA32("<<<<00010101xxxx0000xxxx0xx1xxxx", InstName.Cmp, InstEmit32.Cmp, OpCode32AluRsReg.Create); + SetA32("<<<<00010000xxxxxxxx00000100xxxx", InstName.Crc32b, InstEmit32.Crc32b, OpCode32AluReg.Create); + SetA32("<<<<00010000xxxxxxxx00100100xxxx", InstName.Crc32cb, InstEmit32.Crc32cb, OpCode32AluReg.Create); + SetA32("<<<<00010010xxxxxxxx00100100xxxx", InstName.Crc32ch, InstEmit32.Crc32ch, OpCode32AluReg.Create); + SetA32("<<<<00010100xxxxxxxx00100100xxxx", InstName.Crc32cw, InstEmit32.Crc32cw, OpCode32AluReg.Create); + SetA32("<<<<00010010xxxxxxxx00000100xxxx", InstName.Crc32h, InstEmit32.Crc32h, OpCode32AluReg.Create); + SetA32("<<<<00010100xxxxxxxx00000100xxxx", InstName.Crc32w, InstEmit32.Crc32w, OpCode32AluReg.Create); + SetA32("<<<<0011001000001111000000010100", InstName.Csdb, InstEmit32.Csdb, OpCode32.Create); + SetA32("1111010101111111111100000101xxxx", InstName.Dmb, InstEmit32.Dmb, OpCode32.Create); + SetA32("1111010101111111111100000100xxxx", InstName.Dsb, InstEmit32.Dsb, OpCode32.Create); + SetA32("<<<<0010001xxxxxxxxxxxxxxxxxxxxx", InstName.Eor, InstEmit32.Eor, OpCode32AluImm.Create); + SetA32("<<<<0000001xxxxxxxxxxxxxxxx0xxxx", InstName.Eor, InstEmit32.Eor, OpCode32AluRsImm.Create); + SetA32("<<<<0000001xxxxxxxxxxxxx0xx1xxxx", InstName.Eor, InstEmit32.Eor, OpCode32AluRsReg.Create); + SetA32("<<<<0011001000001111000000010000", InstName.Esb, InstEmit32.Nop, OpCode32.Create); // Error Synchronization Barrier (FEAT_RAS) + SetA32("<<<<001100100000111100000000011x", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<0011001000001111000000001xxx", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<0011001000001111000000010001", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<0011001000001111000000010011", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<0011001000001111000000010101", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<001100100000111100000001011x", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<0011001000001111000000011xxx", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<00110010000011110000001xxxxx", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<0011001000001111000001xxxxxx", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("<<<<001100100000111100001xxxxxxx", InstName.Hint, InstEmit32.Nop, OpCode32.Create); // Reserved Hint + SetA32("1111010101111111111100000110xxxx", InstName.Isb, InstEmit32.Nop, OpCode32.Create); + SetA32("<<<<00011001xxxxxxxx110010011111", InstName.Lda, InstEmit32.Lda, OpCode32MemLdEx.Create); + SetA32("<<<<00011101xxxxxxxx110010011111", InstName.Ldab, InstEmit32.Ldab, OpCode32MemLdEx.Create); + SetA32("<<<<00011001xxxxxxxx111010011111", InstName.Ldaex, InstEmit32.Ldaex, OpCode32MemLdEx.Create); + SetA32("<<<<00011101xxxxxxxx111010011111", InstName.Ldaexb, InstEmit32.Ldaexb, OpCode32MemLdEx.Create); + SetA32("<<<<00011011xxxxxxxx111010011111", InstName.Ldaexd, InstEmit32.Ldaexd, OpCode32MemLdEx.Create); + SetA32("<<<<00011111xxxxxxxx111010011111", InstName.Ldaexh, InstEmit32.Ldaexh, OpCode32MemLdEx.Create); + SetA32("<<<<00011111xxxxxxxx110010011111", InstName.Ldah, InstEmit32.Ldah, OpCode32MemLdEx.Create); + SetA32("<<<<100xx0x1xxxxxxxxxxxxxxxxxxxx", InstName.Ldm, InstEmit32.Ldm, OpCode32MemMult.Create); + SetA32("<<<<010xx0x1xxxxxxxxxxxxxxxxxxxx", InstName.Ldr, InstEmit32.Ldr, OpCode32MemImm.Create); + SetA32("<<<<011xx0x1xxxxxxxxxxxxxxx0xxxx", InstName.Ldr, InstEmit32.Ldr, OpCode32MemRsImm.Create); + SetA32("<<<<010xx1x1xxxxxxxxxxxxxxxxxxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCode32MemImm.Create); + SetA32("<<<<011xx1x1xxxxxxxxxxxxxxx0xxxx", InstName.Ldrb, InstEmit32.Ldrb, OpCode32MemRsImm.Create); + SetA32("<<<<000xx1x0xxxxxxxxxxxx1101xxxx", InstName.Ldrd, InstEmit32.Ldrd, OpCode32MemImm8.Create); + SetA32("<<<<000xx0x0xxxxxxxx00001101xxxx", InstName.Ldrd, InstEmit32.Ldrd, OpCode32MemReg.Create); + SetA32("<<<<00011001xxxxxxxx111110011111", InstName.Ldrex, InstEmit32.Ldrex, OpCode32MemLdEx.Create); + SetA32("<<<<00011101xxxxxxxx111110011111", InstName.Ldrexb, InstEmit32.Ldrexb, OpCode32MemLdEx.Create); + SetA32("<<<<00011011xxxxxxxx111110011111", InstName.Ldrexd, InstEmit32.Ldrexd, OpCode32MemLdEx.Create); + SetA32("<<<<00011111xxxxxxxx111110011111", InstName.Ldrexh, InstEmit32.Ldrexh, OpCode32MemLdEx.Create); + SetA32("<<<<000xx1x1xxxxxxxxxxxx1011xxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCode32MemImm8.Create); + SetA32("<<<<000xx0x1xxxxxxxx00001011xxxx", InstName.Ldrh, InstEmit32.Ldrh, OpCode32MemReg.Create); + SetA32("<<<<000xx1x1xxxxxxxxxxxx1101xxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCode32MemImm8.Create); + SetA32("<<<<000xx0x1xxxxxxxx00001101xxxx", InstName.Ldrsb, InstEmit32.Ldrsb, OpCode32MemReg.Create); + SetA32("<<<<000xx1x1xxxxxxxxxxxx1111xxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCode32MemImm8.Create); + SetA32("<<<<000xx0x1xxxxxxxx00001111xxxx", InstName.Ldrsh, InstEmit32.Ldrsh, OpCode32MemReg.Create); + SetA32("<<<<1110xxx0xxxxxxxx111xxxx1xxxx", InstName.Mcr, InstEmit32.Mcr, OpCode32System.Create); + SetA32("<<<<0000001xxxxxxxxxxxxx1001xxxx", InstName.Mla, InstEmit32.Mla, OpCode32AluMla.Create); + SetA32("<<<<00000110xxxxxxxxxxxx1001xxxx", InstName.Mls, InstEmit32.Mls, OpCode32AluMla.Create); + SetA32("<<<<0011101x0000xxxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCode32AluImm.Create); + SetA32("<<<<0001101x0000xxxxxxxxxxx0xxxx", InstName.Mov, InstEmit32.Mov, OpCode32AluRsImm.Create); + SetA32("<<<<0001101x0000xxxxxxxx0xx1xxxx", InstName.Mov, InstEmit32.Mov, OpCode32AluRsReg.Create); + SetA32("<<<<00110000xxxxxxxxxxxxxxxxxxxx", InstName.Mov, InstEmit32.Mov, OpCode32AluImm16.Create); + SetA32("<<<<00110100xxxxxxxxxxxxxxxxxxxx", InstName.Movt, InstEmit32.Movt, OpCode32AluImm16.Create); + SetA32("<<<<1110xxx1xxxxxxxx111xxxx1xxxx", InstName.Mrc, InstEmit32.Mrc, OpCode32System.Create); + SetA32("<<<<11000101xxxxxxxx111xxxxxxxxx", InstName.Mrrc, InstEmit32.Mrrc, OpCode32System.Create); + SetA32("<<<<00010x001111xxxx000000000000", InstName.Mrs, InstEmit32.Mrs, OpCode32Mrs.Create); + SetA32("<<<<00010x10xxxx111100000000xxxx", InstName.Msr, InstEmit32.Msr, OpCode32MsrReg.Create); + SetA32("<<<<0000000xxxxx0000xxxx1001xxxx", InstName.Mul, InstEmit32.Mul, OpCode32AluMla.Create); + SetA32("<<<<0011111x0000xxxxxxxxxxxxxxxx", InstName.Mvn, InstEmit32.Mvn, OpCode32AluImm.Create); + SetA32("<<<<0001111x0000xxxxxxxxxxx0xxxx", InstName.Mvn, InstEmit32.Mvn, OpCode32AluRsImm.Create); + SetA32("<<<<0001111x0000xxxxxxxx0xx1xxxx", InstName.Mvn, InstEmit32.Mvn, OpCode32AluRsReg.Create); + SetA32("<<<<0011001000001111000000000000", InstName.Nop, InstEmit32.Nop, OpCode32.Create); + SetA32("<<<<0011100xxxxxxxxxxxxxxxxxxxxx", InstName.Orr, InstEmit32.Orr, OpCode32AluImm.Create); + SetA32("<<<<0001100xxxxxxxxxxxxxxxx0xxxx", InstName.Orr, InstEmit32.Orr, OpCode32AluRsImm.Create); + SetA32("<<<<0001100xxxxxxxxxxxxx0xx1xxxx", InstName.Orr, InstEmit32.Orr, OpCode32AluRsReg.Create); + SetA32("<<<<01101000xxxxxxxxxxxxxx01xxxx", InstName.Pkh, InstEmit32.Pkh, OpCode32AluRsImm.Create); + SetA32("11110101xx01xxxx1111xxxxxxxxxxxx", InstName.Pld, InstEmit32.Nop, OpCode32.Create); + SetA32("11110111xx01xxxx1111xxxxxxx0xxxx", InstName.Pld, InstEmit32.Nop, OpCode32.Create); + SetA32("<<<<011011111111xxxx11110011xxxx", InstName.Rbit, InstEmit32.Rbit, OpCode32AluReg.Create); + SetA32("<<<<011010111111xxxx11110011xxxx", InstName.Rev, InstEmit32.Rev, OpCode32AluReg.Create); + SetA32("<<<<011010111111xxxx11111011xxxx", InstName.Rev16, InstEmit32.Rev16, OpCode32AluReg.Create); + SetA32("<<<<011011111111xxxx11111011xxxx", InstName.Revsh, InstEmit32.Revsh, OpCode32AluReg.Create); + SetA32("<<<<0010011xxxxxxxxxxxxxxxxxxxxx", InstName.Rsb, InstEmit32.Rsb, OpCode32AluImm.Create); + SetA32("<<<<0000011xxxxxxxxxxxxxxxx0xxxx", InstName.Rsb, InstEmit32.Rsb, OpCode32AluRsImm.Create); + SetA32("<<<<0000011xxxxxxxxxxxxx0xx1xxxx", InstName.Rsb, InstEmit32.Rsb, OpCode32AluRsReg.Create); + SetA32("<<<<0010111xxxxxxxxxxxxxxxxxxxxx", InstName.Rsc, InstEmit32.Rsc, OpCode32AluImm.Create); + SetA32("<<<<0000111xxxxxxxxxxxxxxxx0xxxx", InstName.Rsc, InstEmit32.Rsc, OpCode32AluRsImm.Create); + SetA32("<<<<0000111xxxxxxxxxxxxx0xx1xxxx", InstName.Rsc, InstEmit32.Rsc, OpCode32AluRsReg.Create); + SetA32("<<<<01100001xxxxxxxx11111001xxxx", InstName.Sadd8, InstEmit32.Sadd8, OpCode32AluReg.Create); + SetA32("<<<<0010110xxxxxxxxxxxxxxxxxxxxx", InstName.Sbc, InstEmit32.Sbc, OpCode32AluImm.Create); + SetA32("<<<<0000110xxxxxxxxxxxxxxxx0xxxx", InstName.Sbc, InstEmit32.Sbc, OpCode32AluRsImm.Create); + SetA32("<<<<0000110xxxxxxxxxxxxx0xx1xxxx", InstName.Sbc, InstEmit32.Sbc, OpCode32AluRsReg.Create); + SetA32("<<<<0111101xxxxxxxxxxxxxx101xxxx", InstName.Sbfx, InstEmit32.Sbfx, OpCode32AluBf.Create); + SetA32("<<<<01110001xxxx1111xxxx0001xxxx", InstName.Sdiv, InstEmit32.Sdiv, OpCode32AluMla.Create); + SetA32("<<<<01101000xxxxxxxx11111011xxxx", InstName.Sel, InstEmit32.Sel, OpCode32AluReg.Create); + SetA32("<<<<0011001000001111000000000100", InstName.Sev, InstEmit32.Nop, OpCode32.Create); + SetA32("<<<<0011001000001111000000000101", InstName.Sevl, InstEmit32.Nop, OpCode32.Create); + SetA32("<<<<01100011xxxxxxxx11111001xxxx", InstName.Shadd8, InstEmit32.Shadd8, OpCode32AluReg.Create); + SetA32("<<<<01100011xxxxxxxx11111111xxxx", InstName.Shsub8, InstEmit32.Shsub8, OpCode32AluReg.Create); + SetA32("<<<<00010000xxxxxxxxxxxx1xx0xxxx", InstName.Smla__, InstEmit32.Smla__, OpCode32AluMla.Create); + SetA32("<<<<0000111xxxxxxxxxxxxx1001xxxx", InstName.Smlal, InstEmit32.Smlal, OpCode32AluUmull.Create); + SetA32("<<<<00010100xxxxxxxxxxxx1xx0xxxx", InstName.Smlal__, InstEmit32.Smlal__, OpCode32AluUmull.Create); + SetA32("<<<<00010010xxxxxxxxxxxx1x00xxxx", InstName.Smlaw_, InstEmit32.Smlaw_, OpCode32AluMla.Create); + SetA32("<<<<01110101xxxxxxxxxxxx00x1xxxx", InstName.Smmla, InstEmit32.Smmla, OpCode32AluMla.Create); + SetA32("<<<<01110101xxxxxxxxxxxx11x1xxxx", InstName.Smmls, InstEmit32.Smmls, OpCode32AluMla.Create); + SetA32("<<<<00010110xxxxxxxxxxxx1xx0xxxx", InstName.Smul__, InstEmit32.Smul__, OpCode32AluMla.Create); + SetA32("<<<<0000110xxxxxxxxxxxxx1001xxxx", InstName.Smull, InstEmit32.Smull, OpCode32AluUmull.Create); + SetA32("<<<<00010010xxxx0000xxxx1x10xxxx", InstName.Smulw_, InstEmit32.Smulw_, OpCode32AluMla.Create); + SetA32("<<<<0110101xxxxxxxxxxxxxxx01xxxx", InstName.Ssat, InstEmit32.Ssat, OpCode32Sat.Create); + SetA32("<<<<01101010xxxxxxxx11110011xxxx", InstName.Ssat16, InstEmit32.Ssat16, OpCode32Sat16.Create); + SetA32("<<<<01100001xxxxxxxx11111111xxxx", InstName.Ssub8, InstEmit32.Ssub8, OpCode32AluReg.Create); + SetA32("<<<<00011000xxxx111111001001xxxx", InstName.Stl, InstEmit32.Stl, OpCode32MemStEx.Create); + SetA32("<<<<00011100xxxx111111001001xxxx", InstName.Stlb, InstEmit32.Stlb, OpCode32MemStEx.Create); + SetA32("<<<<00011000xxxxxxxx11101001xxxx", InstName.Stlex, InstEmit32.Stlex, OpCode32MemStEx.Create); + SetA32("<<<<00011100xxxxxxxx11101001xxxx", InstName.Stlexb, InstEmit32.Stlexb, OpCode32MemStEx.Create); + SetA32("<<<<00011010xxxxxxxx11101001xxxx", InstName.Stlexd, InstEmit32.Stlexd, OpCode32MemStEx.Create); + SetA32("<<<<00011110xxxxxxxx11101001xxxx", InstName.Stlexh, InstEmit32.Stlexh, OpCode32MemStEx.Create); + SetA32("<<<<00011110xxxx111111001001xxxx", InstName.Stlh, InstEmit32.Stlh, OpCode32MemStEx.Create); + SetA32("<<<<100xx0x0xxxxxxxxxxxxxxxxxxxx", InstName.Stm, InstEmit32.Stm, OpCode32MemMult.Create); + SetA32("<<<<010xx0x0xxxxxxxxxxxxxxxxxxxx", InstName.Str, InstEmit32.Str, OpCode32MemImm.Create); + SetA32("<<<<011xx0x0xxxxxxxxxxxxxxx0xxxx", InstName.Str, InstEmit32.Str, OpCode32MemRsImm.Create); + SetA32("<<<<010xx1x0xxxxxxxxxxxxxxxxxxxx", InstName.Strb, InstEmit32.Strb, OpCode32MemImm.Create); + SetA32("<<<<011xx1x0xxxxxxxxxxxxxxx0xxxx", InstName.Strb, InstEmit32.Strb, OpCode32MemRsImm.Create); + SetA32("<<<<000xx1x0xxxxxxxxxxxx1111xxxx", InstName.Strd, InstEmit32.Strd, OpCode32MemImm8.Create); + SetA32("<<<<000xx0x0xxxxxxxx00001111xxxx", InstName.Strd, InstEmit32.Strd, OpCode32MemReg.Create); + SetA32("<<<<00011000xxxxxxxx11111001xxxx", InstName.Strex, InstEmit32.Strex, OpCode32MemStEx.Create); + SetA32("<<<<00011100xxxxxxxx11111001xxxx", InstName.Strexb, InstEmit32.Strexb, OpCode32MemStEx.Create); + SetA32("<<<<00011010xxxxxxxx11111001xxxx", InstName.Strexd, InstEmit32.Strexd, OpCode32MemStEx.Create); + SetA32("<<<<00011110xxxxxxxx11111001xxxx", InstName.Strexh, InstEmit32.Strexh, OpCode32MemStEx.Create); + SetA32("<<<<000xx1x0xxxxxxxxxxxx1011xxxx", InstName.Strh, InstEmit32.Strh, OpCode32MemImm8.Create); + SetA32("<<<<000xx0x0xxxxxxxx00001011xxxx", InstName.Strh, InstEmit32.Strh, OpCode32MemReg.Create); + SetA32("<<<<0010010xxxxxxxxxxxxxxxxxxxxx", InstName.Sub, InstEmit32.Sub, OpCode32AluImm.Create); + SetA32("<<<<0000010xxxxxxxxxxxxxxxx0xxxx", InstName.Sub, InstEmit32.Sub, OpCode32AluRsImm.Create); + SetA32("<<<<0000010xxxxxxxxxxxxx0xx1xxxx", InstName.Sub, InstEmit32.Sub, OpCode32AluRsReg.Create); + SetA32("<<<<1111xxxxxxxxxxxxxxxxxxxxxxxx", InstName.Svc, InstEmit32.Svc, OpCode32Exception.Create); + SetA32("<<<<01101010xxxxxxxxxx000111xxxx", InstName.Sxtb, InstEmit32.Sxtb, OpCode32AluUx.Create); + SetA32("<<<<01101000xxxxxxxxxx000111xxxx", InstName.Sxtb16, InstEmit32.Sxtb16, OpCode32AluUx.Create); + SetA32("<<<<01101011xxxxxxxxxx000111xxxx", InstName.Sxth, InstEmit32.Sxth, OpCode32AluUx.Create); + SetA32("<<<<00110011xxxx0000xxxxxxxxxxxx", InstName.Teq, InstEmit32.Teq, OpCode32AluImm.Create); + SetA32("<<<<00010011xxxx0000xxxxxxx0xxxx", InstName.Teq, InstEmit32.Teq, OpCode32AluRsImm.Create); + SetA32("<<<<00010011xxxx0000xxxx0xx1xxxx", InstName.Teq, InstEmit32.Teq, OpCode32AluRsReg.Create); + SetA32("<<<<0111111111111101111011111110", InstName.Trap, InstEmit32.Trap, OpCode32Exception.Create); + SetA32("<<<<0011001000001111000000010010", InstName.Tsb, InstEmit32.Nop, OpCode32.Create); // Trace Synchronization Barrier (FEAT_TRF) + SetA32("<<<<00110001xxxx0000xxxxxxxxxxxx", InstName.Tst, InstEmit32.Tst, OpCode32AluImm.Create); + SetA32("<<<<00010001xxxx0000xxxxxxx0xxxx", InstName.Tst, InstEmit32.Tst, OpCode32AluRsImm.Create); + SetA32("<<<<00010001xxxx0000xxxx0xx1xxxx", InstName.Tst, InstEmit32.Tst, OpCode32AluRsReg.Create); + SetA32("<<<<01100101xxxxxxxx11111001xxxx", InstName.Uadd8, InstEmit32.Uadd8, OpCode32AluReg.Create); + SetA32("<<<<0111111xxxxxxxxxxxxxx101xxxx", InstName.Ubfx, InstEmit32.Ubfx, OpCode32AluBf.Create); + SetA32("<<<<01110011xxxx1111xxxx0001xxxx", InstName.Udiv, InstEmit32.Udiv, OpCode32AluMla.Create); + SetA32("<<<<01100111xxxxxxxx11111001xxxx", InstName.Uhadd8, InstEmit32.Uhadd8, OpCode32AluReg.Create); + SetA32("<<<<01100111xxxxxxxx11111111xxxx", InstName.Uhsub8, InstEmit32.Uhsub8, OpCode32AluReg.Create); + SetA32("<<<<00000100xxxxxxxxxxxx1001xxxx", InstName.Umaal, InstEmit32.Umaal, OpCode32AluUmull.Create); + SetA32("<<<<0000101xxxxxxxxxxxxx1001xxxx", InstName.Umlal, InstEmit32.Umlal, OpCode32AluUmull.Create); + SetA32("<<<<0000100xxxxxxxxxxxxx1001xxxx", InstName.Umull, InstEmit32.Umull, OpCode32AluUmull.Create); + SetA32("<<<<0110111xxxxxxxxxxxxxxx01xxxx", InstName.Usat, InstEmit32.Usat, OpCode32Sat.Create); + SetA32("<<<<01101110xxxxxxxx11110011xxxx", InstName.Usat16, InstEmit32.Usat16, OpCode32Sat16.Create); + SetA32("<<<<01100101xxxxxxxx11111111xxxx", InstName.Usub8, InstEmit32.Usub8, OpCode32AluReg.Create); + SetA32("<<<<01101110xxxxxxxxxx000111xxxx", InstName.Uxtb, InstEmit32.Uxtb, OpCode32AluUx.Create); + SetA32("<<<<01101100xxxxxxxxxx000111xxxx", InstName.Uxtb16, InstEmit32.Uxtb16, OpCode32AluUx.Create); + SetA32("<<<<01101111xxxxxxxxxx000111xxxx", InstName.Uxth, InstEmit32.Uxth, OpCode32AluUx.Create); + SetA32("<<<<0011001000001111000000000010", InstName.Wfe, InstEmit32.Nop, OpCode32.Create); + SetA32("<<<<0011001000001111000000000011", InstName.Wfi, InstEmit32.Nop, OpCode32.Create); + SetA32("<<<<0011001000001111000000000001", InstName.Yield, InstEmit32.Nop, OpCode32.Create); + + // VFP + SetVfp("<<<<11101x110000xxxx101x11x0xxxx", InstName.Vabs, InstEmit32.Vabs_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11100x11xxxxxxxx101xx0x0xxxx", InstName.Vadd, InstEmit32.Vadd_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11101x11010xxxxx101x01x0xxxx", InstName.Vcmp, InstEmit32.Vcmp, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11101x11010xxxxx101x11x0xxxx", InstName.Vcmpe, InstEmit32.Vcmpe, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11101x110111xxxx101x11x0xxxx", InstName.Vcvt, InstEmit32.Vcvt_FD, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); // FP 32 and 64, scalar. + SetVfp("<<<<11101x11110xxxxx101x11x0xxxx", InstName.Vcvt, InstEmit32.Vcvt_FI, OpCode32SimdCvtFI.Create, OpCode32SimdCvtFI.CreateT32); // FP32 to int. + SetVfp("<<<<11101x111000xxxx101xx1x0xxxx", InstName.Vcvt, InstEmit32.Vcvt_FI, OpCode32SimdCvtFI.Create, OpCode32SimdCvtFI.CreateT32); // Int to FP32. + SetVfp("111111101x1111xxxxxx101xx1x0xxxx", InstName.Vcvt, InstEmit32.Vcvt_RM, OpCode32SimdCvtFI.Create, OpCode32SimdCvtFI.CreateT32); // The many FP32 to int encodings (fp). + SetVfp("<<<<11101x11001xxxxx101xx1x0xxxx", InstName.Vcvt, InstEmit32.Vcvt_TB, OpCode32SimdCvtTB.Create, OpCode32SimdCvtTB.CreateT32); + SetVfp("<<<<11101x00xxxxxxxx101xx0x0xxxx", InstName.Vdiv, InstEmit32.Vdiv_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11101xx0xxxxxxxx1011x0x10000", InstName.Vdup, InstEmit32.Vdup, OpCode32SimdDupGP.Create, OpCode32SimdDupGP.CreateT32); + SetVfp("<<<<11101x10xxxxxxxx101xx0x0xxxx", InstName.Vfma, InstEmit32.Vfma_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11101x10xxxxxxxx101xx1x0xxxx", InstName.Vfms, InstEmit32.Vfms_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11101x01xxxxxxxx101xx1x0xxxx", InstName.Vfnma, InstEmit32.Vfnma_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11101x01xxxxxxxx101xx0x0xxxx", InstName.Vfnms, InstEmit32.Vfnms_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11001x01xxxxxxxx1011xxxxxxx0", InstName.Vldm, InstEmit32.Vldm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11001x11xxxxxxxx1011xxxxxxx0", InstName.Vldm, InstEmit32.Vldm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11010x11xxxxxxxx1011xxxxxxx0", InstName.Vldm, InstEmit32.Vldm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11001x01xxxxxxxx1010xxxxxxxx", InstName.Vldm, InstEmit32.Vldm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11001x11xxxxxxxx1010xxxxxxxx", InstName.Vldm, InstEmit32.Vldm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11010x11xxxxxxxx1010xxxxxxxx", InstName.Vldm, InstEmit32.Vldm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<1101xx01xxxxxxxx101xxxxxxxxx", InstName.Vldr, InstEmit32.Vldr, OpCode32SimdMemImm.Create, OpCode32SimdMemImm.CreateT32); + SetVfp("111111101x00xxxxxxxx10>>x0x0xxxx", InstName.Vmaxnm, InstEmit32.Vmaxnm_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("111111101x00xxxxxxxx10>>x1x0xxxx", InstName.Vminnm, InstEmit32.Vminnm_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11100x00xxxxxxxx101xx0x0xxxx", InstName.Vmla, InstEmit32.Vmla_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11100x00xxxxxxxx101xx1x0xxxx", InstName.Vmls, InstEmit32.Vmls_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11100xx0xxxxxxxx1011xxx10000", InstName.Vmov, InstEmit32.Vmov_G1, OpCode32SimdMovGpElem.Create, OpCode32SimdMovGpElem.CreateT32); // From gen purpose. + SetVfp("<<<<1110xxx1xxxxxxxx1011xxx10000", InstName.Vmov, InstEmit32.Vmov_G1, OpCode32SimdMovGpElem.Create, OpCode32SimdMovGpElem.CreateT32); // To gen purpose. + SetVfp("<<<<1100010xxxxxxxxx101000x1xxxx", InstName.Vmov, InstEmit32.Vmov_G2, OpCode32SimdMovGpDouble.Create, OpCode32SimdMovGpDouble.CreateT32); // To/from gen purpose x2 and single precision x2. + SetVfp("<<<<1100010xxxxxxxxx101100x1xxxx", InstName.Vmov, InstEmit32.Vmov_GD, OpCode32SimdMovGpDouble.Create, OpCode32SimdMovGpDouble.CreateT32); // To/from gen purpose x2 and double precision. + SetVfp("<<<<1110000xxxxxxxxx1010x0010000", InstName.Vmov, InstEmit32.Vmov_GS, OpCode32SimdMovGp.Create, OpCode32SimdMovGp.CreateT32); // To/from gen purpose and single precision. + SetVfp("<<<<11101x11xxxxxxxx101x0000xxxx", InstName.Vmov, InstEmit32.Vmov_I, OpCode32SimdImm44.Create, OpCode32SimdImm44.CreateT32); // Scalar f16/32/64 based on size 01 10 11. + SetVfp("<<<<11101x110000xxxx101x01x0xxxx", InstName.Vmov, InstEmit32.Vmov_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11101111xxxxxxxx101000010000", InstName.Vmrs, InstEmit32.Vmrs, OpCode32SimdSpecial.Create, OpCode32SimdSpecial.CreateT32); + SetVfp("<<<<11101110xxxxxxxx101000010000", InstName.Vmsr, InstEmit32.Vmsr, OpCode32SimdSpecial.Create, OpCode32SimdSpecial.CreateT32); + SetVfp("<<<<11100x10xxxxxxxx101xx0x0xxxx", InstName.Vmul, InstEmit32.Vmul_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11101x110001xxxx101x01x0xxxx", InstName.Vneg, InstEmit32.Vneg_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11100x01xxxxxxxx101xx1x0xxxx", InstName.Vnmla, InstEmit32.Vnmla_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11100x01xxxxxxxx101xx0x0xxxx", InstName.Vnmls, InstEmit32.Vnmls_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("<<<<11100x10xxxxxxxx101xx1x0xxxx", InstName.Vnmul, InstEmit32.Vnmul_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + SetVfp("111111101x1110xxxxxx101x01x0xxxx", InstName.Vrint, InstEmit32.Vrint_RM, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11101x110110xxxx101x11x0xxxx", InstName.Vrint, InstEmit32.Vrint_Z, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11101x110111xxxx101x01x0xxxx", InstName.Vrintx, InstEmit32.Vrintx_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("<<<<11101x110001xxxx101x11x0xxxx", InstName.Vsqrt, InstEmit32.Vsqrt_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32); + SetVfp("111111100xxxxxxxxxxx101xx0x0xxxx", InstName.Vsel, InstEmit32.Vsel, OpCode32SimdSel.Create, OpCode32SimdSel.CreateT32); + SetVfp("<<<<11001x00xxxxxxxx1011xxxxxxx0", InstName.Vstm, InstEmit32.Vstm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11001x10xxxxxxxx1011xxxxxxx0", InstName.Vstm, InstEmit32.Vstm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11010x10xxxxxxxx1011xxxxxxx0", InstName.Vstm, InstEmit32.Vstm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11001x00xxxxxxxx1010xxxxxxxx", InstName.Vstm, InstEmit32.Vstm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11001x10xxxxxxxx1010xxxxxxxx", InstName.Vstm, InstEmit32.Vstm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<11010x10xxxxxxxx1010xxxxxxxx", InstName.Vstm, InstEmit32.Vstm, OpCode32SimdMemMult.Create, OpCode32SimdMemMult.CreateT32); + SetVfp("<<<<1101xx00xxxxxxxx101xxxxxxxxx", InstName.Vstr, InstEmit32.Vstr, OpCode32SimdMemImm.Create, OpCode32SimdMemImm.CreateT32); + SetVfp("<<<<11100x11xxxxxxxx101xx1x0xxxx", InstName.Vsub, InstEmit32.Vsub_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32); + + // ASIMD + SetAsimd("111100111x110000xxx0001101x0xxx0", InstName.Aesd_V, InstEmit32.Aesd_V, OpCode32Simd.Create, OpCode32Simd.CreateT32); + SetAsimd("111100111x110000xxx0001100x0xxx0", InstName.Aese_V, InstEmit32.Aese_V, OpCode32Simd.Create, OpCode32Simd.CreateT32); + SetAsimd("111100111x110000xxx0001111x0xxx0", InstName.Aesimc_V, InstEmit32.Aesimc_V, OpCode32Simd.Create, OpCode32Simd.CreateT32); + SetAsimd("111100111x110000xxx0001110x0xxx0", InstName.Aesmc_V, InstEmit32.Aesmc_V, OpCode32Simd.Create, OpCode32Simd.CreateT32); + SetAsimd("111100110x00xxx0xxx01100x1x0xxx0", InstName.Sha256h_V, InstEmit32.Sha256h_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); + SetAsimd("111100110x01xxx0xxx01100x1x0xxx0", InstName.Sha256h2_V, InstEmit32.Sha256h2_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); + SetAsimd("111100111x111010xxx0001111x0xxx0", InstName.Sha256su0_V, InstEmit32.Sha256su0_V, OpCode32Simd.Create, OpCode32Simd.CreateT32); + SetAsimd("111100110x10xxx0xxx01100x1x0xxx0", InstName.Sha256su1_V, InstEmit32.Sha256su1_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); + SetAsimd("1111001x0x<xxxx", InstName.Vld4, InstEmit32.Vld4, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32); + SetAsimd("111101000x10xxxxxxxx000x<>>xxxxxxx100101x1xxx0", InstName.Vqrshrn, InstEmit32.Vqrshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); + SetAsimd("111100111x>>>xxxxxxx100001x1xxx0", InstName.Vqrshrun, InstEmit32.Vqrshrun, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); + SetAsimd("1111001x1x>>>xxxxxxx100100x1xxx0", InstName.Vqshrn, InstEmit32.Vqshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); + SetAsimd("111100111x>>>xxxxxxx100000x1xxx0", InstName.Vqshrun, InstEmit32.Vqshrun, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); + SetAsimd("1111001x0xxxxxxxxxxx0010xxx1xxxx", InstName.Vqsub, InstEmit32.Vqsub, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); + SetAsimd("111100111x111011xxxx010x0xx0xxxx", InstName.Vrecpe, InstEmit32.Vrecpe, OpCode32SimdSqrte.Create, OpCode32SimdSqrte.CreateT32); + SetAsimd("111100100x00xxxxxxxx1111xxx1xxxx", InstName.Vrecps, InstEmit32.Vrecps, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); + SetAsimd("111100111x11xx00xxxx000<>>xxxxxxx0010>xx1xxxx", InstName.Vrshr, InstEmit32.Vrshr, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32); + SetAsimd("111100101x>>>xxxxxxx100001x1xxx0", InstName.Vrshrn, InstEmit32.Vrshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); + SetAsimd("111100111x111011xxxx010x1xx0xxxx", InstName.Vrsqrte, InstEmit32.Vrsqrte, OpCode32SimdSqrte.Create, OpCode32SimdSqrte.CreateT32); + SetAsimd("111100100x10xxxxxxxx1111xxx1xxxx", InstName.Vrsqrts, InstEmit32.Vrsqrts, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); + SetAsimd("1111001x1x>>>xxxxxxx0011>xx1xxxx", InstName.Vrsra, InstEmit32.Vrsra, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32); + SetAsimd("111100101x>>>xxxxxxx0101>xx1xxxx", InstName.Vshl, InstEmit32.Vshl, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32); + SetAsimd("1111001x0xxxxxxxxxxx0100xxx0xxxx", InstName.Vshl, InstEmit32.Vshl_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32); + SetAsimd("1111001x1x>>>xxxxxxx101000x1xxxx", InstName.Vshll, InstEmit32.Vshll, OpCode32SimdShImmLong.Create, OpCode32SimdShImmLong.CreateT32); // A1 encoding. + SetAsimd("1111001x1x>>>xxxxxxx0000>xx1xxxx", InstName.Vshr, InstEmit32.Vshr, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32); + SetAsimd("111100101x>>>xxxxxxx100000x1xxx0", InstName.Vshrn, InstEmit32.Vshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32); + SetAsimd("1111001x1x>>>xxxxxxx0001>xx1xxxx", InstName.Vsra, InstEmit32.Vsra, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32); + SetAsimd("111101001x00xxxxxxxx0000xxx0xxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32); + SetAsimd("111101001x00xxxxxxxx0100xx0xxxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32); + SetAsimd("111101001x00xxxxxxxx1000x000xxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32); + SetAsimd("111101001x00xxxxxxxx1000x011xxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32); + SetAsimd("111101000x00xxxxxxxx0111xx0xxxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemPair.Create, OpCode32SimdMemPair.CreateT32); // Regs = 1. + SetAsimd("111101000x00xxxxxxxx1010xx<>>>", InstName.It, InstEmit32.It, OpCodeT16IfThen.Create); + SetT16("11000xxxxxxxxxxx", InstName.Stm, InstEmit32.Stm, OpCodeT16MemMult.Create); + SetT16("11001xxxxxxxxxxx", InstName.Ldm, InstEmit32.Ldm, OpCodeT16MemMult.Create); + SetT16("1101<< allInsts, Func ToFastLookupIndex) + { + List[] temp = new List[FastLookupSize]; + + for (int index = 0; index < temp.Length; index++) + { + temp[index] = new List(); + } + + foreach (InstInfo inst in allInsts) + { + int mask = ToFastLookupIndex(inst.Mask); + int value = ToFastLookupIndex(inst.Value); + + for (int index = 0; index < temp.Length; index++) + { + if ((index & mask) == value) + { + temp[index].Add(inst); + } + } + } + + for (int index = 0; index < temp.Length; index++) + { + table[index] = temp[index].ToArray(); + } + } + + private static void SetA32(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp) + { + Set(encoding, AllInstA32, new InstDescriptor(name, emitter), makeOp); + } + + private static void SetT16(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp) + { + encoding = "xxxxxxxxxxxxxxxx" + encoding; + Set(encoding, AllInstT32, new InstDescriptor(name, emitter), makeOp); + } + + private static void SetT32(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp) + { + string reversedEncoding = $"{encoding.AsSpan(16)}{encoding.AsSpan(0, 16)}"; + MakeOp reversedMakeOp = + (inst, address, opCode) + => makeOp(inst, address, (int)BitOperations.RotateRight((uint)opCode, 16)); + Set(reversedEncoding, AllInstT32, new InstDescriptor(name, emitter), reversedMakeOp); + } + + private static void SetVfp(string encoding, InstName name, InstEmitter emitter, MakeOp makeOpA32, MakeOp makeOpT32) + { + SetA32(encoding, name, emitter, makeOpA32); + + string thumbEncoding = encoding; + if (thumbEncoding.StartsWith("<<<<")) + { + thumbEncoding = $"1110{thumbEncoding.AsSpan(4)}"; + } + SetT32(thumbEncoding, name, emitter, makeOpT32); + } + + private static void SetAsimd(string encoding, InstName name, InstEmitter emitter, MakeOp makeOpA32, MakeOp makeOpT32) + { + SetA32(encoding, name, emitter, makeOpA32); + + string thumbEncoding = encoding; + if (thumbEncoding.StartsWith("11110100")) + { + thumbEncoding = $"11111001{encoding.AsSpan(8)}"; + } + else if (thumbEncoding.StartsWith("1111001x")) + { + thumbEncoding = $"111x1111{encoding.AsSpan(8)}"; + } + else if (thumbEncoding.StartsWith("11110010")) + { + thumbEncoding = $"11101111{encoding.AsSpan(8)}"; + } + else if (thumbEncoding.StartsWith("11110011")) + { + thumbEncoding = $"11111111{encoding.AsSpan(8)}"; + } + else + { + throw new ArgumentException("Invalid ASIMD instruction encoding"); + } + SetT32(thumbEncoding, name, emitter, makeOpT32); + } + + private static void SetA64(string encoding, InstName name, InstEmitter emitter, MakeOp makeOp) + { + Set(encoding, AllInstA64, new InstDescriptor(name, emitter), makeOp); + } + + private static void Set(string encoding, List list, InstDescriptor inst, MakeOp makeOp) + { + int bit = encoding.Length - 1; + int value = 0; + int xMask = 0; + int xBits = 0; + + int[] xPos = new int[encoding.Length]; + + int blacklisted = 0; + + for (int index = 0; index < encoding.Length; index++, bit--) + { + // Note: < and > are used on special encodings. + // The < means that we should never have ALL bits with the '<' set. + // So, when the encoding has <<, it means that 00, 01, and 10 are valid, + // but not 11. <<< is 000, 001, ..., 110 but NOT 111, and so on... + // For >, the invalid value is zero. So, for >> 01, 10 and 11 are valid, + // but 00 isn't. + char chr = encoding[index]; + + if (chr == '1') + { + value |= 1 << bit; + } + else if (chr == 'x') + { + xMask |= 1 << bit; + } + else if (chr == '>') + { + xPos[xBits++] = bit; + } + else if (chr == '<') + { + xPos[xBits++] = bit; + + blacklisted |= 1 << bit; + } + else if (chr != '0') + { + throw new ArgumentException(nameof(encoding)); + } + } + + xMask = ~xMask; + + if (xBits == 0) + { + list.Add(new InstInfo(xMask, value, inst, makeOp)); + + return; + } + + for (int index = 0; index < (1 << xBits); index++) + { + int mask = 0; + + for (int x = 0; x < xBits; x++) + { + mask |= ((index >> x) & 1) << xPos[x]; + } + + if (mask != blacklisted) + { + list.Add(new InstInfo(xMask, value | mask, inst, makeOp)); + } + } + } + + public static (InstDescriptor inst, MakeOp makeOp) GetInstA32(int opCode) + { + return GetInstFromList(InstA32FastLookup[ToFastLookupIndexA(opCode)], opCode); + } + + public static (InstDescriptor inst, MakeOp makeOp) GetInstT32(int opCode) + { + return GetInstFromList(InstT32FastLookup[ToFastLookupIndexT(opCode)], opCode); + } + + public static (InstDescriptor inst, MakeOp makeOp) GetInstA64(int opCode) + { + return GetInstFromList(InstA64FastLookup[ToFastLookupIndexA(opCode)], opCode); + } + + private static (InstDescriptor inst, MakeOp makeOp) GetInstFromList(InstInfo[] insts, int opCode) + { + foreach (InstInfo info in insts) + { + if ((opCode & info.Mask) == info.Value) + { + return (info.Inst, info.MakeOp); + } + } + + return (new InstDescriptor(InstName.Und, InstEmit.Und), null); + } + + private static int ToFastLookupIndexA(int value) + { + return ((value >> 10) & 0x00F) | ((value >> 18) & 0xFF0); + } + + private static int ToFastLookupIndexT(int value) + { + return (value >> 4) & 0xFFF; + } + } +} diff --git a/src/ARMeilleure/Decoders/Optimizations/TailCallRemover.cs b/src/ARMeilleure/Decoders/Optimizations/TailCallRemover.cs new file mode 100644 index 00000000..17c17812 --- /dev/null +++ b/src/ARMeilleure/Decoders/Optimizations/TailCallRemover.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; + +namespace ARMeilleure.Decoders.Optimizations +{ + static class TailCallRemover + { + public static Block[] RunPass(ulong entryAddress, List blocks) + { + // Detect tail calls: + // - Assume this function spans the space covered by contiguous code blocks surrounding the entry address. + // - A jump to an area outside this contiguous region will be treated as an exit block. + // - Include a small allowance for jumps outside the contiguous range. + + if (!Decoder.BinarySearch(blocks, entryAddress, out int entryBlockId)) + { + throw new InvalidOperationException("Function entry point is not contained in a block."); + } + + const ulong allowance = 4; + + Block entryBlock = blocks[entryBlockId]; + + Block startBlock = entryBlock; + Block endBlock = entryBlock; + + int startBlockIndex = entryBlockId; + int endBlockIndex = entryBlockId; + + for (int i = entryBlockId + 1; i < blocks.Count; i++) // Search forwards. + { + Block block = blocks[i]; + + if (endBlock.EndAddress < block.Address - allowance) + { + break; // End of contiguous function. + } + + endBlock = block; + endBlockIndex = i; + } + + for (int i = entryBlockId - 1; i >= 0; i--) // Search backwards. + { + Block block = blocks[i]; + + if (startBlock.Address > block.EndAddress + allowance) + { + break; // End of contiguous function. + } + + startBlock = block; + startBlockIndex = i; + } + + if (startBlockIndex == 0 && endBlockIndex == blocks.Count - 1) + { + return blocks.ToArray(); // Nothing to do here. + } + + // Mark branches whose target is outside of the contiguous region as an exit block. + for (int i = startBlockIndex; i <= endBlockIndex; i++) + { + Block block = blocks[i]; + + if (block.Branch != null && (block.Branch.Address > endBlock.EndAddress || block.Branch.EndAddress < startBlock.Address)) + { + block.Branch.Exit = true; + } + } + + var newBlocks = new List(blocks.Count); + + // Finally, rebuild decoded block list, ignoring blocks outside the contiguous range. + for (int i = 0; i < blocks.Count; i++) + { + Block block = blocks[i]; + + if (block.Exit || (i >= startBlockIndex && i <= endBlockIndex)) + { + newBlocks.Add(block); + } + } + + return newBlocks.ToArray(); + } + } +} diff --git a/src/ARMeilleure/Decoders/RegisterSize.cs b/src/ARMeilleure/Decoders/RegisterSize.cs new file mode 100644 index 00000000..c9cea03e --- /dev/null +++ b/src/ARMeilleure/Decoders/RegisterSize.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + enum RegisterSize + { + Int32, + Int64, + Simd64, + Simd128 + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Decoders/ShiftType.cs b/src/ARMeilleure/Decoders/ShiftType.cs new file mode 100644 index 00000000..8583f16a --- /dev/null +++ b/src/ARMeilleure/Decoders/ShiftType.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Decoders +{ + enum ShiftType + { + Lsl = 0, + Lsr = 1, + Asr = 2, + Ror = 3 + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Diagnostics/IRDumper.cs b/src/ARMeilleure/Diagnostics/IRDumper.cs new file mode 100644 index 00000000..3d1a60e5 --- /dev/null +++ b/src/ARMeilleure/Diagnostics/IRDumper.cs @@ -0,0 +1,311 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace ARMeilleure.Diagnostics +{ + class IRDumper + { + private const string Indentation = " "; + + private int _indentLevel; + + private readonly StringBuilder _builder; + + private readonly Dictionary _localNames; + private readonly Dictionary _symbolNames; + + public IRDumper(int indent) + { + _indentLevel = indent; + + _builder = new StringBuilder(); + + _localNames = new Dictionary(); + _symbolNames = new Dictionary(); + } + + private void Indent() + { + _builder.EnsureCapacity(_builder.Capacity + _indentLevel * Indentation.Length); + + for (int index = 0; index < _indentLevel; index++) + { + _builder.Append(Indentation); + } + } + + private void IncreaseIndentation() + { + _indentLevel++; + } + + private void DecreaseIndentation() + { + _indentLevel--; + } + + private void DumpBlockName(BasicBlock block) + { + _builder.Append("block").Append(block.Index); + } + + private void DumpBlockHeader(BasicBlock block) + { + DumpBlockName(block); + + if (block.Frequency == BasicBlockFrequency.Cold) + { + _builder.Append(" cold"); + } + + if (block.SuccessorsCount > 0) + { + _builder.Append(" ("); + + for (int i = 0; i < block.SuccessorsCount; i++) + { + DumpBlockName(block.GetSuccessor(i)); + + if (i < block.SuccessorsCount - 1) + { + _builder.Append(", "); + } + } + + _builder.Append(')'); + } + + _builder.Append(':'); + } + + private void DumpOperand(Operand operand) + { + if (operand == default) + { + _builder.Append(""); + return; + } + + _builder.Append(GetTypeName(operand.Type)).Append(' '); + + switch (operand.Kind) + { + case OperandKind.LocalVariable: + if (!_localNames.TryGetValue(operand, out string localName)) + { + localName = $"%{_localNames.Count}"; + + _localNames.Add(operand, localName); + } + + _builder.Append(localName); + break; + + case OperandKind.Register: + Register reg = operand.GetRegister(); + + switch (reg.Type) + { + case RegisterType.Flag: _builder.Append('b'); break; + case RegisterType.FpFlag: _builder.Append('f'); break; + case RegisterType.Integer: _builder.Append('r'); break; + case RegisterType.Vector: _builder.Append('v'); break; + } + + _builder.Append(reg.Index); + break; + + case OperandKind.Constant: + string symbolName = Symbols.Get(operand.Value); + + if (symbolName != null && !_symbolNames.ContainsKey(operand.Value)) + { + _symbolNames.Add(operand.Value, symbolName); + } + + _builder.Append("0x").Append(operand.Value.ToString("X")); + break; + + case OperandKind.Memory: + var memOp = operand.GetMemory(); + + _builder.Append('['); + + DumpOperand(memOp.BaseAddress); + + if (memOp.Index != default) + { + _builder.Append(" + "); + + DumpOperand(memOp.Index); + + switch (memOp.Scale) + { + case Multiplier.x2: _builder.Append("*2"); break; + case Multiplier.x4: _builder.Append("*4"); break; + case Multiplier.x8: _builder.Append("*8"); break; + } + } + + if (memOp.Displacement != 0) + { + _builder.Append(" + 0x").Append(memOp.Displacement.ToString("X")); + } + + _builder.Append(']'); + break; + + default: + _builder.Append(operand.Type); + break; + } + } + + private void DumpNode(ControlFlowGraph cfg, Operation node) + { + for (int index = 0; index < node.DestinationsCount; index++) + { + DumpOperand(node.GetDestination(index)); + + if (index == node.DestinationsCount - 1) + { + _builder.Append(" = "); + } + else + { + _builder.Append(", "); + } + } + + switch (node) + { + case Operation operation: + if (operation.Instruction == Instruction.Phi) + { + PhiOperation phi = operation.AsPhi(); + + _builder.Append("Phi "); + + for (int index = 0; index < phi.SourcesCount; index++) + { + _builder.Append('('); + + DumpBlockName(phi.GetBlock(cfg, index)); + + _builder.Append(": "); + + DumpOperand(phi.GetSource(index)); + + _builder.Append(')'); + + if (index < phi.SourcesCount - 1) + { + _builder.Append(", "); + } + } + + break; + } + + bool comparison = false; + + _builder.Append(operation.Instruction); + + if (operation.Instruction == Instruction.Extended) + { + _builder.Append('.').Append(operation.Intrinsic); + } + else if (operation.Instruction == Instruction.BranchIf || + operation.Instruction == Instruction.Compare) + { + comparison = true; + } + + _builder.Append(' '); + + for (int index = 0; index < operation.SourcesCount; index++) + { + Operand source = operation.GetSource(index); + + if (index < operation.SourcesCount - 1) + { + DumpOperand(source); + + _builder.Append(", "); + } + else if (comparison) + { + _builder.Append((Comparison)source.AsInt32()); + } + else + { + DumpOperand(source); + } + } + break; + } + + if (_symbolNames.Count == 1) + { + _builder.Append(" ;; ").Append(_symbolNames.First().Value); + } + else if (_symbolNames.Count > 1) + { + _builder.Append(" ;;"); + + foreach ((ulong value, string name) in _symbolNames) + { + _builder.Append(" 0x").Append(value.ToString("X")).Append(" = ").Append(name); + } + } + + // Reset the set of symbols for the next Node we're going to dump. + _symbolNames.Clear(); + } + + public static string GetDump(ControlFlowGraph cfg) + { + var dumper = new IRDumper(1); + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + dumper.Indent(); + dumper.DumpBlockHeader(block); + + dumper._builder.AppendLine(); + + dumper.IncreaseIndentation(); + + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + dumper.Indent(); + dumper.DumpNode(cfg, node); + + dumper._builder.AppendLine(); + } + + dumper.DecreaseIndentation(); + } + + return dumper._builder.ToString(); + } + + private static string GetTypeName(OperandType type) + { + return type switch + { + OperandType.None => "none", + OperandType.I32 => "i32", + OperandType.I64 => "i64", + OperandType.FP32 => "f32", + OperandType.FP64 => "f64", + OperandType.V128 => "v128", + _ => throw new ArgumentException($"Invalid operand type \"{type}\"."), + }; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Diagnostics/Logger.cs b/src/ARMeilleure/Diagnostics/Logger.cs new file mode 100644 index 00000000..07a60667 --- /dev/null +++ b/src/ARMeilleure/Diagnostics/Logger.cs @@ -0,0 +1,56 @@ +using ARMeilleure.Translation; +using System; +using System.Diagnostics; + +namespace ARMeilleure.Diagnostics +{ + static class Logger + { + private static long _startTime; + + private static long[] _accumulatedTime; + + static Logger() + { + _accumulatedTime = new long[(int)PassName.Count]; + } + + [Conditional("M_DEBUG")] + public static void StartPass(PassName name) + { + WriteOutput(name + " pass started..."); + + _startTime = Stopwatch.GetTimestamp(); + } + + [Conditional("M_DEBUG")] + public static void EndPass(PassName name, ControlFlowGraph cfg) + { + EndPass(name); + + WriteOutput("IR after " + name + " pass:"); + + WriteOutput(IRDumper.GetDump(cfg)); + } + + [Conditional("M_DEBUG")] + public static void EndPass(PassName name) + { + long elapsedTime = Stopwatch.GetTimestamp() - _startTime; + + _accumulatedTime[(int)name] += elapsedTime; + + WriteOutput($"{name} pass ended after {GetMilliseconds(_accumulatedTime[(int)name])} ms..."); + } + + private static long GetMilliseconds(long ticks) + { + return (long)(((double)ticks / Stopwatch.Frequency) * 1000); + } + + private static void WriteOutput(string text) + { + Console.WriteLine(text); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Diagnostics/PassName.cs b/src/ARMeilleure/Diagnostics/PassName.cs new file mode 100644 index 00000000..e34bf0d2 --- /dev/null +++ b/src/ARMeilleure/Diagnostics/PassName.cs @@ -0,0 +1,19 @@ +namespace ARMeilleure.Diagnostics +{ + enum PassName + { + Decoding, + Translation, + RegisterUsage, + TailMerge, + Dominance, + SsaConstruction, + RegisterToLocal, + Optimization, + PreAllocation, + RegisterAllocation, + CodeGeneration, + + Count + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Diagnostics/Symbols.cs b/src/ARMeilleure/Diagnostics/Symbols.cs new file mode 100644 index 00000000..6bde62f5 --- /dev/null +++ b/src/ARMeilleure/Diagnostics/Symbols.cs @@ -0,0 +1,84 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; + +namespace ARMeilleure.Diagnostics +{ + static class Symbols + { + private readonly struct RangedSymbol + { + public readonly ulong Start; + public readonly ulong End; + public readonly ulong ElementSize; + public readonly string Name; + + public RangedSymbol(ulong start, ulong end, ulong elemSize, string name) + { + Start = start; + End = end; + ElementSize = elemSize; + Name = name; + } + } + + private static readonly ConcurrentDictionary _symbols; + private static readonly List _rangedSymbols; + + static Symbols() + { + _symbols = new ConcurrentDictionary(); + _rangedSymbols = new List(); + } + + public static string Get(ulong address) + { + string result; + + if (_symbols.TryGetValue(address, out result)) + { + return result; + } + + lock (_rangedSymbols) + { + foreach (RangedSymbol symbol in _rangedSymbols) + { + if (address >= symbol.Start && address <= symbol.End) + { + ulong diff = address - symbol.Start; + ulong rem = diff % symbol.ElementSize; + + result = symbol.Name + "_" + diff / symbol.ElementSize; + + if (rem != 0) + { + result += "+" + rem; + } + + _symbols.TryAdd(address, result); + + return result; + } + } + } + + return null; + } + + [Conditional("M_DEBUG")] + public static void Add(ulong address, string name) + { + _symbols.TryAdd(address, name); + } + + [Conditional("M_DEBUG")] + public static void Add(ulong address, ulong size, ulong elemSize, string name) + { + lock (_rangedSymbols) + { + _rangedSymbols.Add(new RangedSymbol(address, address + size, elemSize, name)); + } + } + } +} diff --git a/src/ARMeilleure/Diagnostics/TranslatorEventSource.cs b/src/ARMeilleure/Diagnostics/TranslatorEventSource.cs new file mode 100644 index 00000000..a4f17844 --- /dev/null +++ b/src/ARMeilleure/Diagnostics/TranslatorEventSource.cs @@ -0,0 +1,67 @@ +using System.Diagnostics.Tracing; +using System.Threading; + +namespace ARMeilleure.Diagnostics +{ + [EventSource(Name = "ARMeilleure")] + class TranslatorEventSource : EventSource + { + public static readonly TranslatorEventSource Log = new(); + + private int _rejitQueue; + private ulong _funcTabSize; + private ulong _funcTabLeafSize; + private PollingCounter _rejitQueueCounter; + private PollingCounter _funcTabSizeCounter; + private PollingCounter _funcTabLeafSizeCounter; + + public TranslatorEventSource() + { + _rejitQueueCounter = new PollingCounter("rejit-queue-length", this, () => _rejitQueue) + { + DisplayName = "Rejit Queue Length" + }; + + _funcTabSizeCounter = new PollingCounter("addr-tab-alloc", this, () => _funcTabSize / 1024d / 1024d) + { + DisplayName = "AddressTable Total Bytes Allocated", + DisplayUnits = "MiB" + }; + + _funcTabLeafSizeCounter = new PollingCounter("addr-tab-leaf-alloc", this, () => _funcTabLeafSize / 1024d / 1024d) + { + DisplayName = "AddressTable Total Leaf Bytes Allocated", + DisplayUnits = "MiB" + }; + } + + public void RejitQueueAdd(int count) + { + Interlocked.Add(ref _rejitQueue, count); + } + + public void AddressTableAllocated(int bytes, bool leaf) + { + _funcTabSize += (uint)bytes; + + if (leaf) + { + _funcTabLeafSize += (uint)bytes; + } + } + + protected override void Dispose(bool disposing) + { + _rejitQueueCounter.Dispose(); + _rejitQueueCounter = null; + + _funcTabLeafSizeCounter.Dispose(); + _funcTabLeafSizeCounter = null; + + _funcTabSizeCounter.Dispose(); + _funcTabSizeCounter = null; + + base.Dispose(disposing); + } + } +} diff --git a/src/ARMeilleure/Instructions/CryptoHelper.cs b/src/ARMeilleure/Instructions/CryptoHelper.cs new file mode 100644 index 00000000..e517c75d --- /dev/null +++ b/src/ARMeilleure/Instructions/CryptoHelper.cs @@ -0,0 +1,280 @@ +// https://www.intel.com/content/dam/doc/white-paper/advanced-encryption-standard-new-instructions-set-paper.pdf + +using ARMeilleure.State; +using System; + +namespace ARMeilleure.Instructions +{ + static class CryptoHelper + { +#region "LookUp Tables" + private static ReadOnlySpan _sBox => new byte[] + { + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 + }; + + private static ReadOnlySpan _invSBox => new byte[] + { + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d + }; + + private static ReadOnlySpan _gfMul02 => new byte[] + { + 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, + 0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e, + 0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, + 0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e, + 0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e, + 0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe, + 0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde, + 0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe, + 0x1b, 0x19, 0x1f, 0x1d, 0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05, + 0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d, 0x23, 0x21, 0x27, 0x25, + 0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55, 0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45, + 0x7b, 0x79, 0x7f, 0x7d, 0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65, + 0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d, 0x83, 0x81, 0x87, 0x85, + 0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5, 0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5, + 0xdb, 0xd9, 0xdf, 0xdd, 0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5, + 0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5 + }; + + private static ReadOnlySpan _gfMul03 => new byte[] + { + 0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d, 0x14, 0x17, 0x12, 0x11, + 0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39, 0x28, 0x2b, 0x2e, 0x2d, 0x24, 0x27, 0x22, 0x21, + 0x60, 0x63, 0x66, 0x65, 0x6c, 0x6f, 0x6a, 0x69, 0x78, 0x7b, 0x7e, 0x7d, 0x74, 0x77, 0x72, 0x71, + 0x50, 0x53, 0x56, 0x55, 0x5c, 0x5f, 0x5a, 0x59, 0x48, 0x4b, 0x4e, 0x4d, 0x44, 0x47, 0x42, 0x41, + 0xc0, 0xc3, 0xc6, 0xc5, 0xcc, 0xcf, 0xca, 0xc9, 0xd8, 0xdb, 0xde, 0xdd, 0xd4, 0xd7, 0xd2, 0xd1, + 0xf0, 0xf3, 0xf6, 0xf5, 0xfc, 0xff, 0xfa, 0xf9, 0xe8, 0xeb, 0xee, 0xed, 0xe4, 0xe7, 0xe2, 0xe1, + 0xa0, 0xa3, 0xa6, 0xa5, 0xac, 0xaf, 0xaa, 0xa9, 0xb8, 0xbb, 0xbe, 0xbd, 0xb4, 0xb7, 0xb2, 0xb1, + 0x90, 0x93, 0x96, 0x95, 0x9c, 0x9f, 0x9a, 0x99, 0x88, 0x8b, 0x8e, 0x8d, 0x84, 0x87, 0x82, 0x81, + 0x9b, 0x98, 0x9d, 0x9e, 0x97, 0x94, 0x91, 0x92, 0x83, 0x80, 0x85, 0x86, 0x8f, 0x8c, 0x89, 0x8a, + 0xab, 0xa8, 0xad, 0xae, 0xa7, 0xa4, 0xa1, 0xa2, 0xb3, 0xb0, 0xb5, 0xb6, 0xbf, 0xbc, 0xb9, 0xba, + 0xfb, 0xf8, 0xfd, 0xfe, 0xf7, 0xf4, 0xf1, 0xf2, 0xe3, 0xe0, 0xe5, 0xe6, 0xef, 0xec, 0xe9, 0xea, + 0xcb, 0xc8, 0xcd, 0xce, 0xc7, 0xc4, 0xc1, 0xc2, 0xd3, 0xd0, 0xd5, 0xd6, 0xdf, 0xdc, 0xd9, 0xda, + 0x5b, 0x58, 0x5d, 0x5e, 0x57, 0x54, 0x51, 0x52, 0x43, 0x40, 0x45, 0x46, 0x4f, 0x4c, 0x49, 0x4a, + 0x6b, 0x68, 0x6d, 0x6e, 0x67, 0x64, 0x61, 0x62, 0x73, 0x70, 0x75, 0x76, 0x7f, 0x7c, 0x79, 0x7a, + 0x3b, 0x38, 0x3d, 0x3e, 0x37, 0x34, 0x31, 0x32, 0x23, 0x20, 0x25, 0x26, 0x2f, 0x2c, 0x29, 0x2a, + 0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02, 0x13, 0x10, 0x15, 0x16, 0x1f, 0x1c, 0x19, 0x1a + }; + + private static ReadOnlySpan _gfMul09 => new byte[] + { + 0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77, + 0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7, + 0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, + 0xab, 0xa2, 0xb9, 0xb0, 0x8f, 0x86, 0x9d, 0x94, 0xe3, 0xea, 0xf1, 0xf8, 0xc7, 0xce, 0xd5, 0xdc, + 0x76, 0x7f, 0x64, 0x6d, 0x52, 0x5b, 0x40, 0x49, 0x3e, 0x37, 0x2c, 0x25, 0x1a, 0x13, 0x08, 0x01, + 0xe6, 0xef, 0xf4, 0xfd, 0xc2, 0xcb, 0xd0, 0xd9, 0xae, 0xa7, 0xbc, 0xb5, 0x8a, 0x83, 0x98, 0x91, + 0x4d, 0x44, 0x5f, 0x56, 0x69, 0x60, 0x7b, 0x72, 0x05, 0x0c, 0x17, 0x1e, 0x21, 0x28, 0x33, 0x3a, + 0xdd, 0xd4, 0xcf, 0xc6, 0xf9, 0xf0, 0xeb, 0xe2, 0x95, 0x9c, 0x87, 0x8e, 0xb1, 0xb8, 0xa3, 0xaa, + 0xec, 0xe5, 0xfe, 0xf7, 0xc8, 0xc1, 0xda, 0xd3, 0xa4, 0xad, 0xb6, 0xbf, 0x80, 0x89, 0x92, 0x9b, + 0x7c, 0x75, 0x6e, 0x67, 0x58, 0x51, 0x4a, 0x43, 0x34, 0x3d, 0x26, 0x2f, 0x10, 0x19, 0x02, 0x0b, + 0xd7, 0xde, 0xc5, 0xcc, 0xf3, 0xfa, 0xe1, 0xe8, 0x9f, 0x96, 0x8d, 0x84, 0xbb, 0xb2, 0xa9, 0xa0, + 0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x0f, 0x06, 0x1d, 0x14, 0x2b, 0x22, 0x39, 0x30, + 0x9a, 0x93, 0x88, 0x81, 0xbe, 0xb7, 0xac, 0xa5, 0xd2, 0xdb, 0xc0, 0xc9, 0xf6, 0xff, 0xe4, 0xed, + 0x0a, 0x03, 0x18, 0x11, 0x2e, 0x27, 0x3c, 0x35, 0x42, 0x4b, 0x50, 0x59, 0x66, 0x6f, 0x74, 0x7d, + 0xa1, 0xa8, 0xb3, 0xba, 0x85, 0x8c, 0x97, 0x9e, 0xe9, 0xe0, 0xfb, 0xf2, 0xcd, 0xc4, 0xdf, 0xd6, + 0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62, 0x5d, 0x54, 0x4f, 0x46 + }; + + private static ReadOnlySpan _gfMul0B => new byte[] + { + 0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69, + 0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81, 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9, + 0x7b, 0x70, 0x6d, 0x66, 0x57, 0x5c, 0x41, 0x4a, 0x23, 0x28, 0x35, 0x3e, 0x0f, 0x04, 0x19, 0x12, + 0xcb, 0xc0, 0xdd, 0xd6, 0xe7, 0xec, 0xf1, 0xfa, 0x93, 0x98, 0x85, 0x8e, 0xbf, 0xb4, 0xa9, 0xa2, + 0xf6, 0xfd, 0xe0, 0xeb, 0xda, 0xd1, 0xcc, 0xc7, 0xae, 0xa5, 0xb8, 0xb3, 0x82, 0x89, 0x94, 0x9f, + 0x46, 0x4d, 0x50, 0x5b, 0x6a, 0x61, 0x7c, 0x77, 0x1e, 0x15, 0x08, 0x03, 0x32, 0x39, 0x24, 0x2f, + 0x8d, 0x86, 0x9b, 0x90, 0xa1, 0xaa, 0xb7, 0xbc, 0xd5, 0xde, 0xc3, 0xc8, 0xf9, 0xf2, 0xef, 0xe4, + 0x3d, 0x36, 0x2b, 0x20, 0x11, 0x1a, 0x07, 0x0c, 0x65, 0x6e, 0x73, 0x78, 0x49, 0x42, 0x5f, 0x54, + 0xf7, 0xfc, 0xe1, 0xea, 0xdb, 0xd0, 0xcd, 0xc6, 0xaf, 0xa4, 0xb9, 0xb2, 0x83, 0x88, 0x95, 0x9e, + 0x47, 0x4c, 0x51, 0x5a, 0x6b, 0x60, 0x7d, 0x76, 0x1f, 0x14, 0x09, 0x02, 0x33, 0x38, 0x25, 0x2e, + 0x8c, 0x87, 0x9a, 0x91, 0xa0, 0xab, 0xb6, 0xbd, 0xd4, 0xdf, 0xc2, 0xc9, 0xf8, 0xf3, 0xee, 0xe5, + 0x3c, 0x37, 0x2a, 0x21, 0x10, 0x1b, 0x06, 0x0d, 0x64, 0x6f, 0x72, 0x79, 0x48, 0x43, 0x5e, 0x55, + 0x01, 0x0a, 0x17, 0x1c, 0x2d, 0x26, 0x3b, 0x30, 0x59, 0x52, 0x4f, 0x44, 0x75, 0x7e, 0x63, 0x68, + 0xb1, 0xba, 0xa7, 0xac, 0x9d, 0x96, 0x8b, 0x80, 0xe9, 0xe2, 0xff, 0xf4, 0xc5, 0xce, 0xd3, 0xd8, + 0x7a, 0x71, 0x6c, 0x67, 0x56, 0x5d, 0x40, 0x4b, 0x22, 0x29, 0x34, 0x3f, 0x0e, 0x05, 0x18, 0x13, + 0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f, 0xbe, 0xb5, 0xa8, 0xa3 + }; + + private static ReadOnlySpan _gfMul0D => new byte[] + { + 0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b, + 0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b, + 0xbb, 0xb6, 0xa1, 0xac, 0x8f, 0x82, 0x95, 0x98, 0xd3, 0xde, 0xc9, 0xc4, 0xe7, 0xea, 0xfd, 0xf0, + 0x6b, 0x66, 0x71, 0x7c, 0x5f, 0x52, 0x45, 0x48, 0x03, 0x0e, 0x19, 0x14, 0x37, 0x3a, 0x2d, 0x20, + 0x6d, 0x60, 0x77, 0x7a, 0x59, 0x54, 0x43, 0x4e, 0x05, 0x08, 0x1f, 0x12, 0x31, 0x3c, 0x2b, 0x26, + 0xbd, 0xb0, 0xa7, 0xaa, 0x89, 0x84, 0x93, 0x9e, 0xd5, 0xd8, 0xcf, 0xc2, 0xe1, 0xec, 0xfb, 0xf6, + 0xd6, 0xdb, 0xcc, 0xc1, 0xe2, 0xef, 0xf8, 0xf5, 0xbe, 0xb3, 0xa4, 0xa9, 0x8a, 0x87, 0x90, 0x9d, + 0x06, 0x0b, 0x1c, 0x11, 0x32, 0x3f, 0x28, 0x25, 0x6e, 0x63, 0x74, 0x79, 0x5a, 0x57, 0x40, 0x4d, + 0xda, 0xd7, 0xc0, 0xcd, 0xee, 0xe3, 0xf4, 0xf9, 0xb2, 0xbf, 0xa8, 0xa5, 0x86, 0x8b, 0x9c, 0x91, + 0x0a, 0x07, 0x10, 0x1d, 0x3e, 0x33, 0x24, 0x29, 0x62, 0x6f, 0x78, 0x75, 0x56, 0x5b, 0x4c, 0x41, + 0x61, 0x6c, 0x7b, 0x76, 0x55, 0x58, 0x4f, 0x42, 0x09, 0x04, 0x13, 0x1e, 0x3d, 0x30, 0x27, 0x2a, + 0xb1, 0xbc, 0xab, 0xa6, 0x85, 0x88, 0x9f, 0x92, 0xd9, 0xd4, 0xc3, 0xce, 0xed, 0xe0, 0xf7, 0xfa, + 0xb7, 0xba, 0xad, 0xa0, 0x83, 0x8e, 0x99, 0x94, 0xdf, 0xd2, 0xc5, 0xc8, 0xeb, 0xe6, 0xf1, 0xfc, + 0x67, 0x6a, 0x7d, 0x70, 0x53, 0x5e, 0x49, 0x44, 0x0f, 0x02, 0x15, 0x18, 0x3b, 0x36, 0x21, 0x2c, + 0x0c, 0x01, 0x16, 0x1b, 0x38, 0x35, 0x22, 0x2f, 0x64, 0x69, 0x7e, 0x73, 0x50, 0x5d, 0x4a, 0x47, + 0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3, 0x80, 0x8d, 0x9a, 0x97 + }; + + private static ReadOnlySpan _gfMul0E => new byte[] + { + 0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a, + 0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba, + 0xdb, 0xd5, 0xc7, 0xc9, 0xe3, 0xed, 0xff, 0xf1, 0xab, 0xa5, 0xb7, 0xb9, 0x93, 0x9d, 0x8f, 0x81, + 0x3b, 0x35, 0x27, 0x29, 0x03, 0x0d, 0x1f, 0x11, 0x4b, 0x45, 0x57, 0x59, 0x73, 0x7d, 0x6f, 0x61, + 0xad, 0xa3, 0xb1, 0xbf, 0x95, 0x9b, 0x89, 0x87, 0xdd, 0xd3, 0xc1, 0xcf, 0xe5, 0xeb, 0xf9, 0xf7, + 0x4d, 0x43, 0x51, 0x5f, 0x75, 0x7b, 0x69, 0x67, 0x3d, 0x33, 0x21, 0x2f, 0x05, 0x0b, 0x19, 0x17, + 0x76, 0x78, 0x6a, 0x64, 0x4e, 0x40, 0x52, 0x5c, 0x06, 0x08, 0x1a, 0x14, 0x3e, 0x30, 0x22, 0x2c, + 0x96, 0x98, 0x8a, 0x84, 0xae, 0xa0, 0xb2, 0xbc, 0xe6, 0xe8, 0xfa, 0xf4, 0xde, 0xd0, 0xc2, 0xcc, + 0x41, 0x4f, 0x5d, 0x53, 0x79, 0x77, 0x65, 0x6b, 0x31, 0x3f, 0x2d, 0x23, 0x09, 0x07, 0x15, 0x1b, + 0xa1, 0xaf, 0xbd, 0xb3, 0x99, 0x97, 0x85, 0x8b, 0xd1, 0xdf, 0xcd, 0xc3, 0xe9, 0xe7, 0xf5, 0xfb, + 0x9a, 0x94, 0x86, 0x88, 0xa2, 0xac, 0xbe, 0xb0, 0xea, 0xe4, 0xf6, 0xf8, 0xd2, 0xdc, 0xce, 0xc0, + 0x7a, 0x74, 0x66, 0x68, 0x42, 0x4c, 0x5e, 0x50, 0x0a, 0x04, 0x16, 0x18, 0x32, 0x3c, 0x2e, 0x20, + 0xec, 0xe2, 0xf0, 0xfe, 0xd4, 0xda, 0xc8, 0xc6, 0x9c, 0x92, 0x80, 0x8e, 0xa4, 0xaa, 0xb8, 0xb6, + 0x0c, 0x02, 0x10, 0x1e, 0x34, 0x3a, 0x28, 0x26, 0x7c, 0x72, 0x60, 0x6e, 0x44, 0x4a, 0x58, 0x56, + 0x37, 0x39, 0x2b, 0x25, 0x0f, 0x01, 0x13, 0x1d, 0x47, 0x49, 0x5b, 0x55, 0x7f, 0x71, 0x63, 0x6d, + 0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d + }; + + private static ReadOnlySpan _srPerm => new byte[] + { + 0, 13, 10, 7, 4, 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3 + }; + + private static ReadOnlySpan _isrPerm => new byte[] + { + 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, 1, 6, 11 + }; +#endregion + + public static V128 AesInvMixColumns(V128 op) + { + byte[] inState = op.ToArray(); + byte[] outState = new byte[16]; + + for (int columns = 0; columns <= 3; columns++) + { + int idx = columns << 2; + + byte row0 = inState[idx + 0]; // A, E, I, M: [row0, col0-col3] + byte row1 = inState[idx + 1]; // B, F, J, N: [row1, col0-col3] + byte row2 = inState[idx + 2]; // C, G, K, O: [row2, col0-col3] + byte row3 = inState[idx + 3]; // D, H, L, P: [row3, col0-col3] + + outState[idx + 0] = (byte)((uint)_gfMul0E[row0] ^ _gfMul0B[row1] ^ _gfMul0D[row2] ^ _gfMul09[row3]); + outState[idx + 1] = (byte)((uint)_gfMul09[row0] ^ _gfMul0E[row1] ^ _gfMul0B[row2] ^ _gfMul0D[row3]); + outState[idx + 2] = (byte)((uint)_gfMul0D[row0] ^ _gfMul09[row1] ^ _gfMul0E[row2] ^ _gfMul0B[row3]); + outState[idx + 3] = (byte)((uint)_gfMul0B[row0] ^ _gfMul0D[row1] ^ _gfMul09[row2] ^ _gfMul0E[row3]); + } + + return new V128(outState); + } + + public static V128 AesInvShiftRows(V128 op) + { + byte[] inState = op.ToArray(); + byte[] outState = new byte[16]; + + for (int idx = 0; idx <= 15; idx++) + { + outState[_isrPerm[idx]] = inState[idx]; + } + + return new V128(outState); + } + + public static V128 AesInvSubBytes(V128 op) + { + byte[] inState = op.ToArray(); + byte[] outState = new byte[16]; + + for (int idx = 0; idx <= 15; idx++) + { + outState[idx] = _invSBox[inState[idx]]; + } + + return new V128(outState); + } + + public static V128 AesMixColumns(V128 op) + { + byte[] inState = op.ToArray(); + byte[] outState = new byte[16]; + + for (int columns = 0; columns <= 3; columns++) + { + int idx = columns << 2; + + byte row0 = inState[idx + 0]; // A, E, I, M: [row0, col0-col3] + byte row1 = inState[idx + 1]; // B, F, J, N: [row1, col0-col3] + byte row2 = inState[idx + 2]; // C, G, K, O: [row2, col0-col3] + byte row3 = inState[idx + 3]; // D, H, L, P: [row3, col0-col3] + + outState[idx + 0] = (byte)((uint)_gfMul02[row0] ^ _gfMul03[row1] ^ row2 ^ row3); + outState[idx + 1] = (byte)((uint)row0 ^ _gfMul02[row1] ^ _gfMul03[row2] ^ row3); + outState[idx + 2] = (byte)((uint)row0 ^ row1 ^ _gfMul02[row2] ^ _gfMul03[row3]); + outState[idx + 3] = (byte)((uint)_gfMul03[row0] ^ row1 ^ row2 ^ _gfMul02[row3]); + } + + return new V128(outState); + } + + public static V128 AesShiftRows(V128 op) + { + byte[] inState = op.ToArray(); + byte[] outState = new byte[16]; + + for (int idx = 0; idx <= 15; idx++) + { + outState[_srPerm[idx]] = inState[idx]; + } + + return new V128(outState); + } + + public static V128 AesSubBytes(V128 op) + { + byte[] inState = op.ToArray(); + byte[] outState = new byte[16]; + + for (int idx = 0; idx <= 15; idx++) + { + outState[idx] = _sBox[inState[idx]]; + } + + return new V128(outState); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitAlu.cs b/src/ARMeilleure/Instructions/InstEmitAlu.cs new file mode 100644 index 00000000..e0d10e77 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitAlu.cs @@ -0,0 +1,400 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System.Diagnostics; + +using static ARMeilleure.Instructions.InstEmitAluHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Adc(ArmEmitterContext context) => EmitAdc(context, setFlags: false); + public static void Adcs(ArmEmitterContext context) => EmitAdc(context, setFlags: true); + + private static void EmitAdc(ArmEmitterContext context, bool setFlags) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand d = context.Add(n, m); + + Operand carry = GetFlag(PState.CFlag); + + if (context.CurrOp.RegisterSize == RegisterSize.Int64) + { + carry = context.ZeroExtend32(OperandType.I64, carry); + } + + d = context.Add(d, carry); + + if (setFlags) + { + EmitNZFlagsCheck(context, d); + + EmitAdcsCCheck(context, n, d); + EmitAddsVCheck(context, n, m, d); + } + + SetAluDOrZR(context, d); + } + + public static void Add(ArmEmitterContext context) + { + SetAluD(context, context.Add(GetAluN(context), GetAluM(context))); + } + + public static void Adds(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + context.MarkComparison(n, m); + + Operand d = context.Add(n, m); + + EmitNZFlagsCheck(context, d); + + EmitAddsCCheck(context, n, d); + EmitAddsVCheck(context, n, m, d); + + SetAluDOrZR(context, d); + } + + public static void And(ArmEmitterContext context) + { + SetAluD(context, context.BitwiseAnd(GetAluN(context), GetAluM(context))); + } + + public static void Ands(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand d = context.BitwiseAnd(n, m); + + EmitNZFlagsCheck(context, d); + EmitCVFlagsClear(context); + + SetAluDOrZR(context, d); + } + + public static void Asrv(ArmEmitterContext context) + { + SetAluDOrZR(context, context.ShiftRightSI(GetAluN(context), GetAluMShift(context))); + } + + public static void Bic(ArmEmitterContext context) => EmitBic(context, setFlags: false); + public static void Bics(ArmEmitterContext context) => EmitBic(context, setFlags: true); + + private static void EmitBic(ArmEmitterContext context, bool setFlags) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand d = context.BitwiseAnd(n, context.BitwiseNot(m)); + + if (setFlags) + { + EmitNZFlagsCheck(context, d); + EmitCVFlagsClear(context); + } + + SetAluD(context, d, setFlags); + } + + public static void Cls(ArmEmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + + Operand nHigh = context.ShiftRightUI(n, Const(1)); + + bool is32Bits = op.RegisterSize == RegisterSize.Int32; + + Operand mask = is32Bits ? Const(int.MaxValue) : Const(long.MaxValue); + + Operand nLow = context.BitwiseAnd(n, mask); + + Operand res = context.CountLeadingZeros(context.BitwiseExclusiveOr(nHigh, nLow)); + + res = context.Subtract(res, Const(res.Type, 1)); + + SetAluDOrZR(context, res); + } + + public static void Clz(ArmEmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + + Operand d = context.CountLeadingZeros(n); + + SetAluDOrZR(context, d); + } + + public static void Eon(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand d = context.BitwiseExclusiveOr(n, context.BitwiseNot(m)); + + SetAluD(context, d); + } + + public static void Eor(ArmEmitterContext context) + { + SetAluD(context, context.BitwiseExclusiveOr(GetAluN(context), GetAluM(context))); + } + + public static void Extr(ArmEmitterContext context) + { + OpCodeAluRs op = (OpCodeAluRs)context.CurrOp; + + Operand res = GetIntOrZR(context, op.Rm); + + if (op.Shift != 0) + { + if (op.Rn == op.Rm) + { + res = context.RotateRight(res, Const(op.Shift)); + } + else + { + res = context.ShiftRightUI(res, Const(op.Shift)); + + Operand n = GetIntOrZR(context, op.Rn); + + int invShift = op.GetBitsCount() - op.Shift; + + res = context.BitwiseOr(res, context.ShiftLeft(n, Const(invShift))); + } + } + + SetAluDOrZR(context, res); + } + + public static void Lslv(ArmEmitterContext context) + { + SetAluDOrZR(context, context.ShiftLeft(GetAluN(context), GetAluMShift(context))); + } + + public static void Lsrv(ArmEmitterContext context) + { + SetAluDOrZR(context, context.ShiftRightUI(GetAluN(context), GetAluMShift(context))); + } + + public static void Sbc(ArmEmitterContext context) => EmitSbc(context, setFlags: false); + public static void Sbcs(ArmEmitterContext context) => EmitSbc(context, setFlags: true); + + private static void EmitSbc(ArmEmitterContext context, bool setFlags) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand d = context.Subtract(n, m); + + Operand borrow = context.BitwiseExclusiveOr(GetFlag(PState.CFlag), Const(1)); + + if (context.CurrOp.RegisterSize == RegisterSize.Int64) + { + borrow = context.ZeroExtend32(OperandType.I64, borrow); + } + + d = context.Subtract(d, borrow); + + if (setFlags) + { + EmitNZFlagsCheck(context, d); + + EmitSbcsCCheck(context, n, m); + EmitSubsVCheck(context, n, m, d); + } + + SetAluDOrZR(context, d); + } + + public static void Sub(ArmEmitterContext context) + { + SetAluD(context, context.Subtract(GetAluN(context), GetAluM(context))); + } + + public static void Subs(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + context.MarkComparison(n, m); + + Operand d = context.Subtract(n, m); + + EmitNZFlagsCheck(context, d); + + EmitSubsCCheck(context, n, m); + EmitSubsVCheck(context, n, m, d); + + SetAluDOrZR(context, d); + } + + public static void Orn(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand d = context.BitwiseOr(n, context.BitwiseNot(m)); + + SetAluD(context, d); + } + + public static void Orr(ArmEmitterContext context) + { + SetAluD(context, context.BitwiseOr(GetAluN(context), GetAluM(context))); + } + + public static void Rbit(ArmEmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + Operand d; + + if (op.RegisterSize == RegisterSize.Int32) + { + d = EmitReverseBits32Op(context, n); + } + else + { + d = EmitReverseBits64Op(context, n); + } + + SetAluDOrZR(context, d); + } + + private static Operand EmitReverseBits64Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I64); + + Operand val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op, Const(0xaaaaaaaaaaaaaaaaul)), Const(1)), + context.ShiftLeft (context.BitwiseAnd(op, Const(0x5555555555555555ul)), Const(1))); + + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xccccccccccccccccul)), Const(2)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x3333333333333333ul)), Const(2))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xf0f0f0f0f0f0f0f0ul)), Const(4)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x0f0f0f0f0f0f0f0ful)), Const(4))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xff00ff00ff00ff00ul)), Const(8)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x00ff00ff00ff00fful)), Const(8))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xffff0000ffff0000ul)), Const(16)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x0000ffff0000fffful)), Const(16))); + + return context.BitwiseOr(context.ShiftRightUI(val, Const(32)), context.ShiftLeft(val, Const(32))); + } + + public static void Rev16(ArmEmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + Operand d; + + if (op.RegisterSize == RegisterSize.Int32) + { + d = EmitReverseBytes16_32Op(context, n); + } + else + { + d = EmitReverseBytes16_64Op(context, n); + } + + SetAluDOrZR(context, d); + } + + public static void Rev32(ArmEmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + Operand d; + + if (op.RegisterSize == RegisterSize.Int32) + { + d = context.ByteSwap(n); + } + else + { + d = EmitReverseBytes32_64Op(context, n); + } + + SetAluDOrZR(context, d); + } + + private static Operand EmitReverseBytes32_64Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I64); + + Operand val = EmitReverseBytes16_64Op(context, op); + + return context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xffff0000ffff0000ul)), Const(16)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x0000ffff0000fffful)), Const(16))); + } + + public static void Rev64(ArmEmitterContext context) + { + OpCodeAlu op = (OpCodeAlu)context.CurrOp; + + SetAluDOrZR(context, context.ByteSwap(GetIntOrZR(context, op.Rn))); + } + + public static void Rorv(ArmEmitterContext context) + { + SetAluDOrZR(context, context.RotateRight(GetAluN(context), GetAluMShift(context))); + } + + private static Operand GetAluMShift(ArmEmitterContext context) + { + IOpCodeAluRs op = (IOpCodeAluRs)context.CurrOp; + + Operand m = GetIntOrZR(context, op.Rm); + + if (op.RegisterSize == RegisterSize.Int64) + { + m = context.ConvertI64ToI32(m); + } + + return context.BitwiseAnd(m, Const(context.CurrOp.GetBitsCount() - 1)); + } + + private static void EmitCVFlagsClear(ArmEmitterContext context) + { + SetFlag(context, PState.CFlag, Const(0)); + SetFlag(context, PState.VFlag, Const(0)); + } + + public static void SetAluD(ArmEmitterContext context, Operand d) + { + SetAluD(context, d, x31IsZR: false); + } + + public static void SetAluDOrZR(ArmEmitterContext context, Operand d) + { + SetAluD(context, d, x31IsZR: true); + } + + public static void SetAluD(ArmEmitterContext context, Operand d, bool x31IsZR) + { + IOpCodeAlu op = (IOpCodeAlu)context.CurrOp; + + if ((x31IsZR || op is IOpCodeAluRs) && op.Rd == RegisterConsts.ZeroIndex) + { + return; + } + + SetIntOrSP(context, op.Rd, d); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitAlu32.cs b/src/ARMeilleure/Instructions/InstEmitAlu32.cs new file mode 100644 index 00000000..584ada7e --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitAlu32.cs @@ -0,0 +1,931 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitAluHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void Add(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.Add(n, m); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + + EmitAddsCCheck(context, n, res); + EmitAddsVCheck(context, n, m, res); + } + + EmitAluStore(context, res); + } + + public static void Adc(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.Add(n, m); + + Operand carry = GetFlag(PState.CFlag); + + res = context.Add(res, carry); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + + EmitAdcsCCheck(context, n, res); + EmitAddsVCheck(context, n, m, res); + } + + EmitAluStore(context, res); + } + + public static void And(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.BitwiseAnd(n, m); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Bfc(ArmEmitterContext context) + { + IOpCode32AluBf op = (IOpCode32AluBf)context.CurrOp; + + Operand d = GetIntA32(context, op.Rd); + Operand res = context.BitwiseAnd(d, Const(~op.DestMask)); + + SetIntA32(context, op.Rd, res); + } + + public static void Bfi(ArmEmitterContext context) + { + IOpCode32AluBf op = (IOpCode32AluBf)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand d = GetIntA32(context, op.Rd); + Operand part = context.BitwiseAnd(n, Const(op.SourceMask)); + + if (op.Lsb != 0) + { + part = context.ShiftLeft(part, Const(op.Lsb)); + } + + Operand res = context.BitwiseAnd(d, Const(~op.DestMask)); + res = context.BitwiseOr(res, context.BitwiseAnd(part, Const(op.DestMask))); + + SetIntA32(context, op.Rd, res); + } + + public static void Bic(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.BitwiseAnd(n, context.BitwiseNot(m)); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Clz(ArmEmitterContext context) + { + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.CountLeadingZeros(m); + EmitAluStore(context, res); + } + + public static void Cmp(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.Subtract(n, m); + + EmitNZFlagsCheck(context, res); + + EmitSubsCCheck(context, n, res); + EmitSubsVCheck(context, n, m, res); + } + + public static void Cmn(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.Add(n, m); + + EmitNZFlagsCheck(context, res); + + EmitAddsCCheck(context, n, res); + EmitAddsVCheck(context, n, m, res); + } + + public static void Eor(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.BitwiseExclusiveOr(n, m); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Mov(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand m = GetAluM(context); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, m); + } + + EmitAluStore(context, m); + } + + public static void Movt(ArmEmitterContext context) + { + IOpCode32AluImm16 op = (IOpCode32AluImm16)context.CurrOp; + + Operand d = GetIntA32(context, op.Rd); + Operand imm = Const(op.Immediate << 16); // Immeditate value as top halfword. + Operand res = context.BitwiseAnd(d, Const(0x0000ffff)); + res = context.BitwiseOr(res, imm); + + EmitAluStore(context, res); + } + + public static void Mul(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.Multiply(n, m); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Mvn(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + Operand m = GetAluM(context); + + Operand res = context.BitwiseNot(m); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Orr(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.BitwiseOr(n, m); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Orn(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.BitwiseOr(n, context.BitwiseNot(m)); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Pkh(ArmEmitterContext context) + { + OpCode32AluRsImm op = (OpCode32AluRsImm)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res; + + bool tbform = op.ShiftType == ShiftType.Asr; + if (tbform) + { + res = context.BitwiseOr(context.BitwiseAnd(n, Const(0xFFFF0000)), context.BitwiseAnd(m, Const(0xFFFF))); + } + else + { + res = context.BitwiseOr(context.BitwiseAnd(m, Const(0xFFFF0000)), context.BitwiseAnd(n, Const(0xFFFF))); + } + + EmitAluStore(context, res); + } + + public static void Rbit(ArmEmitterContext context) + { + Operand m = GetAluM(context); + + Operand res = EmitReverseBits32Op(context, m); + + EmitAluStore(context, res); + } + + public static void Rev(ArmEmitterContext context) + { + Operand m = GetAluM(context); + + Operand res = context.ByteSwap(m); + + EmitAluStore(context, res); + } + + public static void Rev16(ArmEmitterContext context) + { + Operand m = GetAluM(context); + + Operand res = EmitReverseBytes16_32Op(context, m); + + EmitAluStore(context, res); + } + + public static void Revsh(ArmEmitterContext context) + { + Operand m = GetAluM(context); + + Operand res = EmitReverseBytes16_32Op(context, m); + + EmitAluStore(context, context.SignExtend16(OperandType.I32, res)); + } + + public static void Rsc(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.Subtract(m, n); + + Operand borrow = context.BitwiseExclusiveOr(GetFlag(PState.CFlag), Const(1)); + + res = context.Subtract(res, borrow); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + + EmitSbcsCCheck(context, m, n); + EmitSubsVCheck(context, m, n, res); + } + + EmitAluStore(context, res); + } + + public static void Rsb(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.Subtract(m, n); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + + EmitSubsCCheck(context, m, res); + EmitSubsVCheck(context, m, n, res); + } + + EmitAluStore(context, res); + } + + public static void Sadd8(ArmEmitterContext context) + { + EmitAddSub8(context, add: true, unsigned: false); + } + + public static void Sbc(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.Subtract(n, m); + + Operand borrow = context.BitwiseExclusiveOr(GetFlag(PState.CFlag), Const(1)); + + res = context.Subtract(res, borrow); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + + EmitSbcsCCheck(context, n, m); + EmitSubsVCheck(context, n, m, res); + } + + EmitAluStore(context, res); + } + + public static void Sbfx(ArmEmitterContext context) + { + IOpCode32AluBf op = (IOpCode32AluBf)context.CurrOp; + + var msb = op.Lsb + op.Msb; // For this instruction, the msb is actually a width. + + Operand n = GetIntA32(context, op.Rn); + Operand res = context.ShiftRightSI(context.ShiftLeft(n, Const(31 - msb)), Const(31 - op.Msb)); + + SetIntA32(context, op.Rd, res); + } + + public static void Sdiv(ArmEmitterContext context) + { + EmitDiv(context, unsigned: false); + } + + public static void Sel(ArmEmitterContext context) + { + IOpCode32AluReg op = (IOpCode32AluReg)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + + Operand ge0 = context.ZeroExtend8(OperandType.I32, context.Negate(GetFlag(PState.GE0Flag))); + Operand ge1 = context.ZeroExtend8(OperandType.I32, context.Negate(GetFlag(PState.GE1Flag))); + Operand ge2 = context.ZeroExtend8(OperandType.I32, context.Negate(GetFlag(PState.GE2Flag))); + Operand ge3 = context.Negate(GetFlag(PState.GE3Flag)); + + Operand mask = context.BitwiseOr(ge0, context.ShiftLeft(ge1, Const(8))); + mask = context.BitwiseOr(mask, context.ShiftLeft(ge2, Const(16))); + mask = context.BitwiseOr(mask, context.ShiftLeft(ge3, Const(24))); + + Operand res = context.BitwiseOr(context.BitwiseAnd(n, mask), context.BitwiseAnd(m, context.BitwiseNot(mask))); + + SetIntA32(context, op.Rd, res); + } + + public static void Shadd8(ArmEmitterContext context) + { + EmitHadd8(context, unsigned: false); + } + + public static void Shsub8(ArmEmitterContext context) + { + EmitHsub8(context, unsigned: false); + } + + public static void Ssat(ArmEmitterContext context) + { + OpCode32Sat op = (OpCode32Sat)context.CurrOp; + + EmitSat(context, -(1 << op.SatImm), (1 << op.SatImm) - 1); + } + + public static void Ssat16(ArmEmitterContext context) + { + OpCode32Sat16 op = (OpCode32Sat16)context.CurrOp; + + EmitSat16(context, -(1 << op.SatImm), (1 << op.SatImm) - 1); + } + + public static void Ssub8(ArmEmitterContext context) + { + EmitAddSub8(context, add: false, unsigned: false); + } + + public static void Sub(ArmEmitterContext context) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context, setCarry: false); + + Operand res = context.Subtract(n, m); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + + EmitSubsCCheck(context, n, res); + EmitSubsVCheck(context, n, m, res); + } + + EmitAluStore(context, res); + } + + public static void Sxtb(ArmEmitterContext context) + { + EmitSignExtend(context, true, 8); + } + + public static void Sxtb16(ArmEmitterContext context) + { + EmitExtend16(context, true); + } + + public static void Sxth(ArmEmitterContext context) + { + EmitSignExtend(context, true, 16); + } + + public static void Teq(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.BitwiseExclusiveOr(n, m); + + EmitNZFlagsCheck(context, res); + } + + public static void Tst(ArmEmitterContext context) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + Operand res = context.BitwiseAnd(n, m); + EmitNZFlagsCheck(context, res); + } + + public static void Uadd8(ArmEmitterContext context) + { + EmitAddSub8(context, add: true, unsigned: true); + } + + public static void Ubfx(ArmEmitterContext context) + { + IOpCode32AluBf op = (IOpCode32AluBf)context.CurrOp; + + var msb = op.Lsb + op.Msb; // For this instruction, the msb is actually a width. + + Operand n = GetIntA32(context, op.Rn); + Operand res = context.ShiftRightUI(context.ShiftLeft(n, Const(31 - msb)), Const(31 - op.Msb)); + + SetIntA32(context, op.Rd, res); + } + + public static void Udiv(ArmEmitterContext context) + { + EmitDiv(context, unsigned: true); + } + + public static void Uhadd8(ArmEmitterContext context) + { + EmitHadd8(context, unsigned: true); + } + + public static void Uhsub8(ArmEmitterContext context) + { + EmitHsub8(context, unsigned: true); + } + + public static void Usat(ArmEmitterContext context) + { + OpCode32Sat op = (OpCode32Sat)context.CurrOp; + + EmitSat(context, 0, op.SatImm == 32 ? (int)(~0) : (1 << op.SatImm) - 1); + } + + public static void Usat16(ArmEmitterContext context) + { + OpCode32Sat16 op = (OpCode32Sat16)context.CurrOp; + + EmitSat16(context, 0, (1 << op.SatImm) - 1); + } + + public static void Usub8(ArmEmitterContext context) + { + EmitAddSub8(context, add: false, unsigned: true); + } + + public static void Uxtb(ArmEmitterContext context) + { + EmitSignExtend(context, false, 8); + } + + public static void Uxtb16(ArmEmitterContext context) + { + EmitExtend16(context, false); + } + + public static void Uxth(ArmEmitterContext context) + { + EmitSignExtend(context, false, 16); + } + + private static void EmitSignExtend(ArmEmitterContext context, bool signed, int bits) + { + IOpCode32AluUx op = (IOpCode32AluUx)context.CurrOp; + + Operand m = GetAluM(context); + Operand res; + + if (op.RotateBits == 0) + { + res = m; + } + else + { + Operand rotate = Const(op.RotateBits); + res = context.RotateRight(m, rotate); + } + + switch (bits) + { + case 8: + res = (signed) ? context.SignExtend8(OperandType.I32, res) : context.ZeroExtend8(OperandType.I32, res); + break; + case 16: + res = (signed) ? context.SignExtend16(OperandType.I32, res) : context.ZeroExtend16(OperandType.I32, res); + break; + } + + if (op.Add) + { + res = context.Add(res, GetAluN(context)); + } + + EmitAluStore(context, res); + } + + private static void EmitExtend16(ArmEmitterContext context, bool signed) + { + IOpCode32AluUx op = (IOpCode32AluUx)context.CurrOp; + + Operand m = GetAluM(context); + Operand res; + + if (op.RotateBits == 0) + { + res = m; + } + else + { + Operand rotate = Const(op.RotateBits); + res = context.RotateRight(m, rotate); + } + + Operand low16, high16; + if (signed) + { + low16 = context.SignExtend8(OperandType.I32, res); + high16 = context.SignExtend8(OperandType.I32, context.ShiftRightUI(res, Const(16))); + } + else + { + low16 = context.ZeroExtend8(OperandType.I32, res); + high16 = context.ZeroExtend8(OperandType.I32, context.ShiftRightUI(res, Const(16))); + } + + if (op.Add) + { + Operand n = GetAluN(context); + Operand lowAdd, highAdd; + if (signed) + { + lowAdd = context.SignExtend16(OperandType.I32, n); + highAdd = context.SignExtend16(OperandType.I32, context.ShiftRightUI(n, Const(16))); + } + else + { + lowAdd = context.ZeroExtend16(OperandType.I32, n); + highAdd = context.ZeroExtend16(OperandType.I32, context.ShiftRightUI(n, Const(16))); + } + + low16 = context.Add(low16, lowAdd); + high16 = context.Add(high16, highAdd); + } + + res = context.BitwiseOr( + context.ZeroExtend16(OperandType.I32, low16), + context.ShiftLeft(context.ZeroExtend16(OperandType.I32, high16), Const(16))); + + EmitAluStore(context, res); + } + + private static void EmitDiv(ArmEmitterContext context, bool unsigned) + { + Operand n = GetAluN(context); + Operand m = GetAluM(context); + Operand zero = Const(m.Type, 0); + + Operand divisorIsZero = context.ICompareEqual(m, zero); + + Operand lblBadDiv = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBadDiv, divisorIsZero); + + if (!unsigned) + { + // ARM64 behaviour: If Rn == INT_MIN && Rm == -1, Rd = INT_MIN (overflow). + // TODO: tests to ensure A32 works the same + + Operand intMin = Const(int.MinValue); + Operand minus1 = Const(-1); + + Operand nIsIntMin = context.ICompareEqual(n, intMin); + Operand mIsMinus1 = context.ICompareEqual(m, minus1); + + Operand lblGoodDiv = Label(); + + context.BranchIfFalse(lblGoodDiv, context.BitwiseAnd(nIsIntMin, mIsMinus1)); + + EmitAluStore(context, intMin); + + context.Branch(lblEnd); + + context.MarkLabel(lblGoodDiv); + } + + Operand res = unsigned + ? context.DivideUI(n, m) + : context.Divide(n, m); + + EmitAluStore(context, res); + + context.Branch(lblEnd); + + context.MarkLabel(lblBadDiv); + + EmitAluStore(context, zero); + + context.MarkLabel(lblEnd); + } + + private static void EmitAddSub8(ArmEmitterContext context, bool add, bool unsigned) + { + IOpCode32AluReg op = (IOpCode32AluReg)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + + Operand res = Const(0); + + for (int byteSel = 0; byteSel < 4; byteSel++) + { + Operand shift = Const(byteSel * 8); + + Operand nByte = context.ShiftRightUI(n, shift); + Operand mByte = context.ShiftRightUI(m, shift); + + nByte = unsigned ? context.ZeroExtend8(OperandType.I32, nByte) : context.SignExtend8(OperandType.I32, nByte); + mByte = unsigned ? context.ZeroExtend8(OperandType.I32, mByte) : context.SignExtend8(OperandType.I32, mByte); + + Operand resByte = add ? context.Add(nByte, mByte) : context.Subtract(nByte, mByte); + + res = context.BitwiseOr(res, context.ShiftLeft(context.ZeroExtend8(OperandType.I32, resByte), shift)); + + SetFlag(context, PState.GE0Flag + byteSel, unsigned && add + ? context.ShiftRightUI(resByte, Const(8)) + : context.ShiftRightUI(context.BitwiseNot(resByte), Const(31))); + } + + SetIntA32(context, op.Rd, res); + } + + private static void EmitHadd8(ArmEmitterContext context, bool unsigned) + { + IOpCode32AluReg op = (IOpCode32AluReg)context.CurrOp; + + Operand m = GetIntA32(context, op.Rm); + Operand n = GetIntA32(context, op.Rn); + + Operand xor, res, carry; + + // This relies on the equality x+y == ((x&y) << 1) + (x^y). + // Note that x^y always contains the LSB of the result. + // Since we want to calculate (x+y)/2, we can instead calculate (x&y) + ((x^y)>>1). + // We mask by 0x7F to remove the LSB so that it doesn't leak into the field below. + + res = context.BitwiseAnd(m, n); + carry = context.BitwiseExclusiveOr(m, n); + xor = context.ShiftRightUI(carry, Const(1)); + xor = context.BitwiseAnd(xor, Const(0x7F7F7F7Fu)); + res = context.Add(res, xor); + + if (!unsigned) + { + // Propagates the sign bit from (x^y)>>1 upwards by one. + carry = context.BitwiseAnd(carry, Const(0x80808080u)); + res = context.BitwiseExclusiveOr(res, carry); + } + + SetIntA32(context, op.Rd, res); + } + + private static void EmitHsub8(ArmEmitterContext context, bool unsigned) + { + IOpCode32AluReg op = (IOpCode32AluReg)context.CurrOp; + + Operand m = GetIntA32(context, op.Rm); + Operand n = GetIntA32(context, op.Rn); + Operand left, right, carry, res; + + // This relies on the equality x-y == (x^y) - (((x^y)&y) << 1). + // Note that x^y always contains the LSB of the result. + // Since we want to calculate (x+y)/2, we can instead calculate ((x^y)>>1) - ((x^y)&y). + + carry = context.BitwiseExclusiveOr(m, n); + left = context.ShiftRightUI(carry, Const(1)); + right = context.BitwiseAnd(carry, m); + + // We must now perform a partitioned subtraction. + // We can do this because minuend contains 7 bit fields. + // We use the extra bit in minuend as a bit to borrow from; we set this bit. + // We invert this bit at the end as this tells us if that bit was borrowed from. + + res = context.BitwiseOr(left, Const(0x80808080)); + res = context.Subtract(res, right); + res = context.BitwiseExclusiveOr(res, Const(0x80808080)); + + if (!unsigned) + { + // We then sign extend the result into this bit. + carry = context.BitwiseAnd(carry, Const(0x80808080)); + res = context.BitwiseExclusiveOr(res, carry); + } + + SetIntA32(context, op.Rd, res); + } + + private static void EmitSat(ArmEmitterContext context, int intMin, int intMax) + { + OpCode32Sat op = (OpCode32Sat)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + + int shift = DecodeImmShift(op.ShiftType, op.Imm5); + + switch (op.ShiftType) + { + case ShiftType.Lsl: + if (shift == 32) + { + n = Const(0); + } + else + { + n = context.ShiftLeft(n, Const(shift)); + } + break; + case ShiftType.Asr: + if (shift == 32) + { + n = context.ShiftRightSI(n, Const(31)); + } + else + { + n = context.ShiftRightSI(n, Const(shift)); + } + break; + } + + Operand lblCheckLtIntMin = Label(); + Operand lblNoSat = Label(); + Operand lblEnd = Label(); + + context.BranchIfFalse(lblCheckLtIntMin, context.ICompareGreater(n, Const(intMax))); + + SetFlag(context, PState.QFlag, Const(1)); + SetIntA32(context, op.Rd, Const(intMax)); + context.Branch(lblEnd); + + context.MarkLabel(lblCheckLtIntMin); + context.BranchIfFalse(lblNoSat, context.ICompareLess(n, Const(intMin))); + + SetFlag(context, PState.QFlag, Const(1)); + SetIntA32(context, op.Rd, Const(intMin)); + context.Branch(lblEnd); + + context.MarkLabel(lblNoSat); + + SetIntA32(context, op.Rd, n); + + context.MarkLabel(lblEnd); + } + + private static void EmitSat16(ArmEmitterContext context, int intMin, int intMax) + { + OpCode32Sat16 op = (OpCode32Sat16)context.CurrOp; + + void SetD(int part, Operand value) + { + if (part == 0) + { + SetIntA32(context, op.Rd, context.ZeroExtend16(OperandType.I32, value)); + } + else + { + SetIntA32(context, op.Rd, context.BitwiseOr(GetIntA32(context, op.Rd), context.ShiftLeft(value, Const(16)))); + } + } + + Operand n = GetIntA32(context, op.Rn); + + Operand nLow = context.SignExtend16(OperandType.I32, n); + Operand nHigh = context.ShiftRightSI(n, Const(16)); + + for (int part = 0; part < 2; part++) + { + Operand nPart = part == 0 ? nLow : nHigh; + + Operand lblCheckLtIntMin = Label(); + Operand lblNoSat = Label(); + Operand lblEnd = Label(); + + context.BranchIfFalse(lblCheckLtIntMin, context.ICompareGreater(nPart, Const(intMax))); + + SetFlag(context, PState.QFlag, Const(1)); + SetD(part, Const(intMax)); + context.Branch(lblEnd); + + context.MarkLabel(lblCheckLtIntMin); + context.BranchIfFalse(lblNoSat, context.ICompareLess(nPart, Const(intMin))); + + SetFlag(context, PState.QFlag, Const(1)); + SetD(part, Const(intMin)); + context.Branch(lblEnd); + + context.MarkLabel(lblNoSat); + + SetD(part, nPart); + + context.MarkLabel(lblEnd); + } + } + + private static void EmitAluStore(ArmEmitterContext context, Operand value) + { + IOpCode32Alu op = (IOpCode32Alu)context.CurrOp; + + EmitGenericAluStoreA32(context, op.Rd, ShouldSetFlags(context), value); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitAluHelper.cs b/src/ARMeilleure/Instructions/InstEmitAluHelper.cs new file mode 100644 index 00000000..994878ad --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitAluHelper.cs @@ -0,0 +1,613 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitAluHelper + { + public static bool ShouldSetFlags(ArmEmitterContext context) + { + IOpCode32HasSetFlags op = (IOpCode32HasSetFlags)context.CurrOp; + + if (op.SetFlags == null) + { + return !context.IsInIfThenBlock; + } + + return op.SetFlags.Value; + } + + public static void EmitNZFlagsCheck(ArmEmitterContext context, Operand d) + { + SetFlag(context, PState.NFlag, context.ICompareLess (d, Const(d.Type, 0))); + SetFlag(context, PState.ZFlag, context.ICompareEqual(d, Const(d.Type, 0))); + } + + public static void EmitAdcsCCheck(ArmEmitterContext context, Operand n, Operand d) + { + // C = (Rd == Rn && CIn) || Rd < Rn + Operand cIn = GetFlag(PState.CFlag); + + Operand cOut = context.BitwiseAnd(context.ICompareEqual(d, n), cIn); + + cOut = context.BitwiseOr(cOut, context.ICompareLessUI(d, n)); + + SetFlag(context, PState.CFlag, cOut); + } + + public static void EmitAddsCCheck(ArmEmitterContext context, Operand n, Operand d) + { + // C = Rd < Rn + SetFlag(context, PState.CFlag, context.ICompareLessUI(d, n)); + } + + public static void EmitAddsVCheck(ArmEmitterContext context, Operand n, Operand m, Operand d) + { + // V = (Rd ^ Rn) & ~(Rn ^ Rm) < 0 + Operand vOut = context.BitwiseExclusiveOr(d, n); + + vOut = context.BitwiseAnd(vOut, context.BitwiseNot(context.BitwiseExclusiveOr(n, m))); + + vOut = context.ICompareLess(vOut, Const(vOut.Type, 0)); + + SetFlag(context, PState.VFlag, vOut); + } + + public static void EmitSbcsCCheck(ArmEmitterContext context, Operand n, Operand m) + { + // C = (Rn == Rm && CIn) || Rn > Rm + Operand cIn = GetFlag(PState.CFlag); + + Operand cOut = context.BitwiseAnd(context.ICompareEqual(n, m), cIn); + + cOut = context.BitwiseOr(cOut, context.ICompareGreaterUI(n, m)); + + SetFlag(context, PState.CFlag, cOut); + } + + public static void EmitSubsCCheck(ArmEmitterContext context, Operand n, Operand m) + { + // C = Rn >= Rm + SetFlag(context, PState.CFlag, context.ICompareGreaterOrEqualUI(n, m)); + } + + public static void EmitSubsVCheck(ArmEmitterContext context, Operand n, Operand m, Operand d) + { + // V = (Rd ^ Rn) & (Rn ^ Rm) < 0 + Operand vOut = context.BitwiseExclusiveOr(d, n); + + vOut = context.BitwiseAnd(vOut, context.BitwiseExclusiveOr(n, m)); + + vOut = context.ICompareLess(vOut, Const(vOut.Type, 0)); + + SetFlag(context, PState.VFlag, vOut); + } + + public static Operand EmitReverseBits32Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I32); + + Operand val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op, Const(0xaaaaaaaau)), Const(1)), + context.ShiftLeft(context.BitwiseAnd(op, Const(0x55555555u)), Const(1))); + + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xccccccccu)), Const(2)), + context.ShiftLeft(context.BitwiseAnd(val, Const(0x33333333u)), Const(2))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xf0f0f0f0u)), Const(4)), + context.ShiftLeft(context.BitwiseAnd(val, Const(0x0f0f0f0fu)), Const(4))); + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xff00ff00u)), Const(8)), + context.ShiftLeft(context.BitwiseAnd(val, Const(0x00ff00ffu)), Const(8))); + + return context.BitwiseOr(context.ShiftRightUI(val, Const(16)), context.ShiftLeft(val, Const(16))); + } + + public static Operand EmitReverseBytes16_64Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I64); + + return context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op, Const(0xff00ff00ff00ff00ul)), Const(8)), + context.ShiftLeft(context.BitwiseAnd(op, Const(0x00ff00ff00ff00fful)), Const(8))); + } + + public static Operand EmitReverseBytes16_32Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I32); + + Operand val = EmitReverseBytes16_64Op(context, context.ZeroExtend32(OperandType.I64, op)); + + return context.ConvertI64ToI32(val); + } + + private static void EmitAluWritePc(ArmEmitterContext context, Operand value) + { + Debug.Assert(value.Type == OperandType.I32); + + if (((OpCode32)context.CurrOp).IsThumb) + { + bool isReturn = IsA32Return(context); + if (!isReturn) + { + context.StoreToContext(); + } + + InstEmitFlowHelper.EmitVirtualJump(context, value, isReturn); + } + else + { + EmitBxWritePc(context, value); + } + } + + public static void EmitGenericAluStoreA32(ArmEmitterContext context, int rd, bool setFlags, Operand value) + { + Debug.Assert(value.Type == OperandType.I32); + + if (rd == RegisterAlias.Aarch32Pc && setFlags) + { + if (setFlags) + { + // TODO: Load SPSR etc. + + EmitBxWritePc(context, value); + } + else + { + EmitAluWritePc(context, value); + } + } + else + { + SetIntA32(context, rd, value); + } + } + + public static Operand GetAluN(ArmEmitterContext context) + { + if (context.CurrOp is IOpCodeAlu op) + { + if (op.DataOp == DataOp.Logical || op is IOpCodeAluRs) + { + return GetIntOrZR(context, op.Rn); + } + else + { + return GetIntOrSP(context, op.Rn); + } + } + else if (context.CurrOp is IOpCode32Alu op32) + { + return GetIntA32(context, op32.Rn); + } + else + { + throw InvalidOpCodeType(context.CurrOp); + } + } + + public static Operand GetAluM(ArmEmitterContext context, bool setCarry = true) + { + switch (context.CurrOp) + { + // ARM32. + case IOpCode32AluImm op: + { + if (ShouldSetFlags(context) && op.IsRotated && setCarry) + { + SetFlag(context, PState.CFlag, Const((uint)op.Immediate >> 31)); + } + + return Const(op.Immediate); + } + + case IOpCode32AluImm16 op: return Const(op.Immediate); + + case IOpCode32AluRsImm op: return GetMShiftedByImmediate(context, op, setCarry); + case IOpCode32AluRsReg op: return GetMShiftedByReg(context, op, setCarry); + + case IOpCode32AluReg op: return GetIntA32(context, op.Rm); + + // ARM64. + case IOpCodeAluImm op: + { + if (op.GetOperandType() == OperandType.I32) + { + return Const((int)op.Immediate); + } + else + { + return Const(op.Immediate); + } + } + + case IOpCodeAluRs op: + { + Operand value = GetIntOrZR(context, op.Rm); + + switch (op.ShiftType) + { + case ShiftType.Lsl: value = context.ShiftLeft (value, Const(op.Shift)); break; + case ShiftType.Lsr: value = context.ShiftRightUI(value, Const(op.Shift)); break; + case ShiftType.Asr: value = context.ShiftRightSI(value, Const(op.Shift)); break; + case ShiftType.Ror: value = context.RotateRight (value, Const(op.Shift)); break; + } + + return value; + } + + case IOpCodeAluRx op: + { + Operand value = GetExtendedM(context, op.Rm, op.IntType); + + value = context.ShiftLeft(value, Const(op.Shift)); + + return value; + } + + default: throw InvalidOpCodeType(context.CurrOp); + } + } + + private static Exception InvalidOpCodeType(OpCode opCode) + { + return new InvalidOperationException($"Invalid OpCode type \"{opCode?.GetType().Name ?? "null"}\"."); + } + + // ARM32 helpers. + public static Operand GetMShiftedByImmediate(ArmEmitterContext context, IOpCode32AluRsImm op, bool setCarry) + { + Operand m = GetIntA32(context, op.Rm); + + int shift = op.Immediate; + + if (shift == 0) + { + switch (op.ShiftType) + { + case ShiftType.Lsr: shift = 32; break; + case ShiftType.Asr: shift = 32; break; + case ShiftType.Ror: shift = 1; break; + } + } + + if (shift != 0) + { + setCarry &= ShouldSetFlags(context); + + switch (op.ShiftType) + { + case ShiftType.Lsl: m = GetLslC(context, m, setCarry, shift); break; + case ShiftType.Lsr: m = GetLsrC(context, m, setCarry, shift); break; + case ShiftType.Asr: m = GetAsrC(context, m, setCarry, shift); break; + case ShiftType.Ror: + if (op.Immediate != 0) + { + m = GetRorC(context, m, setCarry, shift); + } + else + { + m = GetRrxC(context, m, setCarry); + } + break; + } + } + + return m; + } + + public static int DecodeImmShift(ShiftType shiftType, int shift) + { + if (shift == 0) + { + switch (shiftType) + { + case ShiftType.Lsr: shift = 32; break; + case ShiftType.Asr: shift = 32; break; + case ShiftType.Ror: shift = 1; break; + } + } + + return shift; + } + + public static Operand GetMShiftedByReg(ArmEmitterContext context, IOpCode32AluRsReg op, bool setCarry) + { + Operand m = GetIntA32(context, op.Rm); + Operand s = context.ZeroExtend8(OperandType.I32, GetIntA32(context, op.Rs)); + Operand shiftIsZero = context.ICompareEqual(s, Const(0)); + + Operand zeroResult = m; + Operand shiftResult = m; + + setCarry &= ShouldSetFlags(context); + + switch (op.ShiftType) + { + case ShiftType.Lsl: shiftResult = EmitLslC(context, m, setCarry, s, shiftIsZero); break; + case ShiftType.Lsr: shiftResult = EmitLsrC(context, m, setCarry, s, shiftIsZero); break; + case ShiftType.Asr: shiftResult = EmitAsrC(context, m, setCarry, s, shiftIsZero); break; + case ShiftType.Ror: shiftResult = EmitRorC(context, m, setCarry, s, shiftIsZero); break; + } + + return context.ConditionalSelect(shiftIsZero, zeroResult, shiftResult); + } + + public static void EmitIfHelper(ArmEmitterContext context, Operand boolValue, Action action, bool expected = true) + { + Debug.Assert(boolValue.Type == OperandType.I32); + + Operand endLabel = Label(); + + if (expected) + { + context.BranchIfFalse(endLabel, boolValue); + } + else + { + context.BranchIfTrue(endLabel, boolValue); + } + + action(); + + context.MarkLabel(endLabel); + } + + public static Operand EmitLslC(ArmEmitterContext context, Operand m, bool setCarry, Operand shift, Operand shiftIsZero) + { + Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32 && shiftIsZero.Type == OperandType.I32); + + Operand shiftLarge = context.ICompareGreaterOrEqual(shift, Const(32)); + Operand result = context.ShiftLeft(m, shift); + if (setCarry) + { + EmitIfHelper(context, shiftIsZero, () => + { + Operand cOut = context.ShiftRightUI(m, context.Subtract(Const(32), shift)); + + cOut = context.BitwiseAnd(cOut, Const(1)); + cOut = context.ConditionalSelect(context.ICompareGreater(shift, Const(32)), Const(0), cOut); + + SetFlag(context, PState.CFlag, cOut); + }, false); + } + + return context.ConditionalSelect(shiftLarge, Const(0), result); + } + + public static Operand GetLslC(ArmEmitterContext context, Operand m, bool setCarry, int shift) + { + Debug.Assert(m.Type == OperandType.I32); + + if ((uint)shift > 32) + { + return GetShiftByMoreThan32(context, setCarry); + } + else if (shift == 32) + { + if (setCarry) + { + SetCarryMLsb(context, m); + } + + return Const(0); + } + else + { + if (setCarry) + { + Operand cOut = context.ShiftRightUI(m, Const(32 - shift)); + + cOut = context.BitwiseAnd(cOut, Const(1)); + + SetFlag(context, PState.CFlag, cOut); + } + + return context.ShiftLeft(m, Const(shift)); + } + } + + public static Operand EmitLsrC(ArmEmitterContext context, Operand m, bool setCarry, Operand shift, Operand shiftIsZero) + { + Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32 && shiftIsZero.Type == OperandType.I32); + + Operand shiftLarge = context.ICompareGreaterOrEqual(shift, Const(32)); + Operand result = context.ShiftRightUI(m, shift); + if (setCarry) + { + EmitIfHelper(context, shiftIsZero, () => + { + Operand cOut = context.ShiftRightUI(m, context.Subtract(shift, Const(1))); + + cOut = context.BitwiseAnd(cOut, Const(1)); + cOut = context.ConditionalSelect(context.ICompareGreater(shift, Const(32)), Const(0), cOut); + + SetFlag(context, PState.CFlag, cOut); + }, false); + } + + return context.ConditionalSelect(shiftLarge, Const(0), result); + } + + public static Operand GetLsrC(ArmEmitterContext context, Operand m, bool setCarry, int shift) + { + Debug.Assert(m.Type == OperandType.I32); + + if ((uint)shift > 32) + { + return GetShiftByMoreThan32(context, setCarry); + } + else if (shift == 32) + { + if (setCarry) + { + SetCarryMMsb(context, m); + } + + return Const(0); + } + else + { + if (setCarry) + { + SetCarryMShrOut(context, m, shift); + } + + return context.ShiftRightUI(m, Const(shift)); + } + } + + private static Operand GetShiftByMoreThan32(ArmEmitterContext context, bool setCarry) + { + if (setCarry) + { + SetFlag(context, PState.CFlag, Const(0)); + } + + return Const(0); + } + + public static Operand EmitAsrC(ArmEmitterContext context, Operand m, bool setCarry, Operand shift, Operand shiftIsZero) + { + Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32 && shiftIsZero.Type == OperandType.I32); + + Operand l32Result; + Operand ge32Result; + + Operand less32 = context.ICompareLess(shift, Const(32)); + + ge32Result = context.ShiftRightSI(m, Const(31)); + + if (setCarry) + { + EmitIfHelper(context, context.BitwiseOr(less32, shiftIsZero), () => + { + SetCarryMLsb(context, ge32Result); + }, false); + } + + l32Result = context.ShiftRightSI(m, shift); + if (setCarry) + { + EmitIfHelper(context, context.BitwiseAnd(less32, context.BitwiseNot(shiftIsZero)), () => + { + Operand cOut = context.ShiftRightUI(m, context.Subtract(shift, Const(1))); + + cOut = context.BitwiseAnd(cOut, Const(1)); + + SetFlag(context, PState.CFlag, cOut); + }); + } + + return context.ConditionalSelect(less32, l32Result, ge32Result); + } + + public static Operand GetAsrC(ArmEmitterContext context, Operand m, bool setCarry, int shift) + { + Debug.Assert(m.Type == OperandType.I32); + + if ((uint)shift >= 32) + { + m = context.ShiftRightSI(m, Const(31)); + + if (setCarry) + { + SetCarryMLsb(context, m); + } + + return m; + } + else + { + if (setCarry) + { + SetCarryMShrOut(context, m, shift); + } + + return context.ShiftRightSI(m, Const(shift)); + } + } + + public static Operand EmitRorC(ArmEmitterContext context, Operand m, bool setCarry, Operand shift, Operand shiftIsZero) + { + Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32 && shiftIsZero.Type == OperandType.I32); + + shift = context.BitwiseAnd(shift, Const(0x1f)); + m = context.RotateRight(m, shift); + + if (setCarry) + { + EmitIfHelper(context, shiftIsZero, () => + { + SetCarryMMsb(context, m); + }, false); + } + + return m; + } + + public static Operand GetRorC(ArmEmitterContext context, Operand m, bool setCarry, int shift) + { + Debug.Assert(m.Type == OperandType.I32); + + shift &= 0x1f; + + m = context.RotateRight(m, Const(shift)); + + if (setCarry) + { + SetCarryMMsb(context, m); + } + + return m; + } + + public static Operand GetRrxC(ArmEmitterContext context, Operand m, bool setCarry) + { + Debug.Assert(m.Type == OperandType.I32); + + // Rotate right by 1 with carry. + Operand cIn = context.Copy(GetFlag(PState.CFlag)); + + if (setCarry) + { + SetCarryMLsb(context, m); + } + + m = context.ShiftRightUI(m, Const(1)); + + m = context.BitwiseOr(m, context.ShiftLeft(cIn, Const(31))); + + return m; + } + + private static void SetCarryMLsb(ArmEmitterContext context, Operand m) + { + Debug.Assert(m.Type == OperandType.I32); + + SetFlag(context, PState.CFlag, context.BitwiseAnd(m, Const(1))); + } + + private static void SetCarryMMsb(ArmEmitterContext context, Operand m) + { + Debug.Assert(m.Type == OperandType.I32); + + SetFlag(context, PState.CFlag, context.ShiftRightUI(m, Const(31))); + } + + private static void SetCarryMShrOut(ArmEmitterContext context, Operand m, int shift) + { + Debug.Assert(m.Type == OperandType.I32); + + Operand cOut = context.ShiftRightUI(m, Const(shift - 1)); + + cOut = context.BitwiseAnd(cOut, Const(1)); + + SetFlag(context, PState.CFlag, cOut); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitBfm.cs b/src/ARMeilleure/Instructions/InstEmitBfm.cs new file mode 100644 index 00000000..46a7dddd --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitBfm.cs @@ -0,0 +1,196 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Bfm(ArmEmitterContext context) + { + OpCodeBfm op = (OpCodeBfm)context.CurrOp; + + Operand d = GetIntOrZR(context, op.Rd); + Operand n = GetIntOrZR(context, op.Rn); + + Operand res; + + if (op.Pos < op.Shift) + { + // BFI. + int shift = op.GetBitsCount() - op.Shift; + + int width = op.Pos + 1; + + long mask = (long)(ulong.MaxValue >> (64 - width)); + + res = context.ShiftLeft(context.BitwiseAnd(n, Const(n.Type, mask)), Const(shift)); + + res = context.BitwiseOr(res, context.BitwiseAnd(d, Const(d.Type, ~(mask << shift)))); + } + else + { + // BFXIL. + int shift = op.Shift; + + int width = op.Pos - shift + 1; + + long mask = (long)(ulong.MaxValue >> (64 - width)); + + res = context.BitwiseAnd(context.ShiftRightUI(n, Const(shift)), Const(n.Type, mask)); + + res = context.BitwiseOr(res, context.BitwiseAnd(d, Const(d.Type, ~mask))); + } + + SetIntOrZR(context, op.Rd, res); + } + + public static void Sbfm(ArmEmitterContext context) + { + OpCodeBfm op = (OpCodeBfm)context.CurrOp; + + int bitsCount = op.GetBitsCount(); + + if (op.Pos + 1 == bitsCount) + { + EmitSbfmShift(context); + } + else if (op.Pos < op.Shift) + { + EmitSbfiz(context); + } + else if (op.Pos == 7 && op.Shift == 0) + { + Operand n = GetIntOrZR(context, op.Rn); + + SetIntOrZR(context, op.Rd, context.SignExtend8(n.Type, n)); + } + else if (op.Pos == 15 && op.Shift == 0) + { + Operand n = GetIntOrZR(context, op.Rn); + + SetIntOrZR(context, op.Rd, context.SignExtend16(n.Type, n)); + } + else if (op.Pos == 31 && op.Shift == 0) + { + Operand n = GetIntOrZR(context, op.Rn); + + SetIntOrZR(context, op.Rd, context.SignExtend32(n.Type, n)); + } + else + { + Operand res = GetIntOrZR(context, op.Rn); + + res = context.ShiftLeft (res, Const(bitsCount - 1 - op.Pos)); + res = context.ShiftRightSI(res, Const(bitsCount - 1)); + res = context.BitwiseAnd (res, Const(res.Type, ~op.TMask)); + + Operand n2 = GetBfmN(context); + + SetIntOrZR(context, op.Rd, context.BitwiseOr(res, n2)); + } + } + + public static void Ubfm(ArmEmitterContext context) + { + OpCodeBfm op = (OpCodeBfm)context.CurrOp; + + if (op.Pos + 1 == op.GetBitsCount()) + { + EmitUbfmShift(context); + } + else if (op.Pos < op.Shift) + { + EmitUbfiz(context); + } + else if (op.Pos + 1 == op.Shift) + { + EmitBfmLsl(context); + } + else if (op.Pos == 7 && op.Shift == 0) + { + Operand n = GetIntOrZR(context, op.Rn); + + SetIntOrZR(context, op.Rd, context.BitwiseAnd(n, Const(n.Type, 0xff))); + } + else if (op.Pos == 15 && op.Shift == 0) + { + Operand n = GetIntOrZR(context, op.Rn); + + SetIntOrZR(context, op.Rd, context.BitwiseAnd(n, Const(n.Type, 0xffff))); + } + else + { + SetIntOrZR(context, op.Rd, GetBfmN(context)); + } + } + + private static void EmitSbfiz(ArmEmitterContext context) => EmitBfiz(context, signed: true); + private static void EmitUbfiz(ArmEmitterContext context) => EmitBfiz(context, signed: false); + + private static void EmitBfiz(ArmEmitterContext context, bool signed) + { + OpCodeBfm op = (OpCodeBfm)context.CurrOp; + + int width = op.Pos + 1; + + Operand res = GetIntOrZR(context, op.Rn); + + res = context.ShiftLeft(res, Const(op.GetBitsCount() - width)); + + res = signed + ? context.ShiftRightSI(res, Const(op.Shift - width)) + : context.ShiftRightUI(res, Const(op.Shift - width)); + + SetIntOrZR(context, op.Rd, res); + } + + private static void EmitSbfmShift(ArmEmitterContext context) + { + EmitBfmShift(context, signed: true); + } + + private static void EmitUbfmShift(ArmEmitterContext context) + { + EmitBfmShift(context, signed: false); + } + + private static void EmitBfmShift(ArmEmitterContext context, bool signed) + { + OpCodeBfm op = (OpCodeBfm)context.CurrOp; + + Operand res = GetIntOrZR(context, op.Rn); + + res = signed + ? context.ShiftRightSI(res, Const(op.Shift)) + : context.ShiftRightUI(res, Const(op.Shift)); + + SetIntOrZR(context, op.Rd, res); + } + + private static void EmitBfmLsl(ArmEmitterContext context) + { + OpCodeBfm op = (OpCodeBfm)context.CurrOp; + + Operand res = GetIntOrZR(context, op.Rn); + + int shift = op.GetBitsCount() - op.Shift; + + SetIntOrZR(context, op.Rd, context.ShiftLeft(res, Const(shift))); + } + + private static Operand GetBfmN(ArmEmitterContext context) + { + OpCodeBfm op = (OpCodeBfm)context.CurrOp; + + Operand res = GetIntOrZR(context, op.Rn); + + long mask = op.WMask & op.TMask; + + return context.BitwiseAnd(context.RotateRight(res, Const(op.Shift)), Const(res.Type, mask)); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Instructions/InstEmitCcmp.cs b/src/ARMeilleure/Instructions/InstEmitCcmp.cs new file mode 100644 index 00000000..7f0beb6c --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitCcmp.cs @@ -0,0 +1,61 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitAluHelper; +using static ARMeilleure.Instructions.InstEmitFlowHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Ccmn(ArmEmitterContext context) => EmitCcmp(context, isNegated: true); + public static void Ccmp(ArmEmitterContext context) => EmitCcmp(context, isNegated: false); + + private static void EmitCcmp(ArmEmitterContext context, bool isNegated) + { + OpCodeCcmp op = (OpCodeCcmp)context.CurrOp; + + Operand lblTrue = Label(); + Operand lblEnd = Label(); + + EmitCondBranch(context, lblTrue, op.Cond); + + SetFlag(context, PState.VFlag, Const((op.Nzcv >> 0) & 1)); + SetFlag(context, PState.CFlag, Const((op.Nzcv >> 1) & 1)); + SetFlag(context, PState.ZFlag, Const((op.Nzcv >> 2) & 1)); + SetFlag(context, PState.NFlag, Const((op.Nzcv >> 3) & 1)); + + context.Branch(lblEnd); + + context.MarkLabel(lblTrue); + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + + if (isNegated) + { + Operand d = context.Add(n, m); + + EmitNZFlagsCheck(context, d); + + EmitAddsCCheck(context, n, d); + EmitAddsVCheck(context, n, m, d); + } + else + { + Operand d = context.Subtract(n, m); + + EmitNZFlagsCheck(context, d); + + EmitSubsCCheck(context, n, m); + EmitSubsVCheck(context, n, m, d); + } + + context.MarkLabel(lblEnd); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Instructions/InstEmitCsel.cs b/src/ARMeilleure/Instructions/InstEmitCsel.cs new file mode 100644 index 00000000..926b9a9e --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitCsel.cs @@ -0,0 +1,53 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitFlowHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + private enum CselOperation + { + None, + Increment, + Invert, + Negate + } + + public static void Csel(ArmEmitterContext context) => EmitCsel(context, CselOperation.None); + public static void Csinc(ArmEmitterContext context) => EmitCsel(context, CselOperation.Increment); + public static void Csinv(ArmEmitterContext context) => EmitCsel(context, CselOperation.Invert); + public static void Csneg(ArmEmitterContext context) => EmitCsel(context, CselOperation.Negate); + + private static void EmitCsel(ArmEmitterContext context, CselOperation cselOp) + { + OpCodeCsel op = (OpCodeCsel)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + Operand m = GetIntOrZR(context, op.Rm); + + if (cselOp == CselOperation.Increment) + { + m = context.Add(m, Const(m.Type, 1)); + } + else if (cselOp == CselOperation.Invert) + { + m = context.BitwiseNot(m); + } + else if (cselOp == CselOperation.Negate) + { + m = context.Negate(m); + } + + Operand condTrue = GetCondTrue(context, op.Cond); + + Operand d = context.ConditionalSelect(condTrue, n, m); + + SetIntOrZR(context, op.Rd, d); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Instructions/InstEmitDiv.cs b/src/ARMeilleure/Instructions/InstEmitDiv.cs new file mode 100644 index 00000000..39a5c32e --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitDiv.cs @@ -0,0 +1,67 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Sdiv(ArmEmitterContext context) => EmitDiv(context, unsigned: false); + public static void Udiv(ArmEmitterContext context) => EmitDiv(context, unsigned: true); + + private static void EmitDiv(ArmEmitterContext context, bool unsigned) + { + OpCodeAluBinary op = (OpCodeAluBinary)context.CurrOp; + + // If Rm == 0, Rd = 0 (division by zero). + Operand n = GetIntOrZR(context, op.Rn); + Operand m = GetIntOrZR(context, op.Rm); + + Operand divisorIsZero = context.ICompareEqual(m, Const(m.Type, 0)); + + Operand lblBadDiv = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBadDiv, divisorIsZero); + + if (!unsigned) + { + // If Rn == INT_MIN && Rm == -1, Rd = INT_MIN (overflow). + bool is32Bits = op.RegisterSize == RegisterSize.Int32; + + Operand intMin = is32Bits ? Const(int.MinValue) : Const(long.MinValue); + Operand minus1 = is32Bits ? Const(-1) : Const(-1L); + + Operand nIsIntMin = context.ICompareEqual(n, intMin); + Operand mIsMinus1 = context.ICompareEqual(m, minus1); + + Operand lblGoodDiv = Label(); + + context.BranchIfFalse(lblGoodDiv, context.BitwiseAnd(nIsIntMin, mIsMinus1)); + + SetAluDOrZR(context, intMin); + + context.Branch(lblEnd); + + context.MarkLabel(lblGoodDiv); + } + + Operand d = unsigned + ? context.DivideUI(n, m) + : context.Divide (n, m); + + SetAluDOrZR(context, d); + + context.Branch(lblEnd); + + context.MarkLabel(lblBadDiv); + + SetAluDOrZR(context, Const(op.GetOperandType(), 0)); + + context.MarkLabel(lblEnd); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitException.cs b/src/ARMeilleure/Instructions/InstEmitException.cs new file mode 100644 index 00000000..0baaa87d --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitException.cs @@ -0,0 +1,55 @@ +using ARMeilleure.Decoders; +using ARMeilleure.Translation; + +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Brk(ArmEmitterContext context) + { + OpCodeException op = (OpCodeException)context.CurrOp; + + string name = nameof(NativeInterface.Break); + + context.StoreToContext(); + + context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.Id)); + + context.LoadFromContext(); + + context.Return(Const(op.Address)); + } + + public static void Svc(ArmEmitterContext context) + { + OpCodeException op = (OpCodeException)context.CurrOp; + + string name = nameof(NativeInterface.SupervisorCall); + + context.StoreToContext(); + + context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.Id)); + + context.LoadFromContext(); + + Translator.EmitSynchronization(context); + } + + public static void Und(ArmEmitterContext context) + { + OpCode op = context.CurrOp; + + string name = nameof(NativeInterface.Undefined); + + context.StoreToContext(); + + context.Call(typeof(NativeInterface).GetMethod(name), Const(op.Address), Const(op.RawOpCode)); + + context.LoadFromContext(); + + context.Return(Const(op.Address)); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Instructions/InstEmitException32.cs b/src/ARMeilleure/Instructions/InstEmitException32.cs new file mode 100644 index 00000000..ec0c32bf --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitException32.cs @@ -0,0 +1,39 @@ +using ARMeilleure.Decoders; +using ARMeilleure.Translation; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void Svc(ArmEmitterContext context) + { + IOpCode32Exception op = (IOpCode32Exception)context.CurrOp; + + string name = nameof(NativeInterface.SupervisorCall); + + context.StoreToContext(); + + context.Call(typeof(NativeInterface).GetMethod(name), Const(((IOpCode)op).Address), Const(op.Id)); + + context.LoadFromContext(); + + Translator.EmitSynchronization(context); + } + + public static void Trap(ArmEmitterContext context) + { + IOpCode32Exception op = (IOpCode32Exception)context.CurrOp; + + string name = nameof(NativeInterface.Break); + + context.StoreToContext(); + + context.Call(typeof(NativeInterface).GetMethod(name), Const(((IOpCode)op).Address), Const(op.Id)); + + context.LoadFromContext(); + + context.Return(Const(context.CurrOp.Address)); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitFlow.cs b/src/ARMeilleure/Instructions/InstEmitFlow.cs new file mode 100644 index 00000000..c40eb55c --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitFlow.cs @@ -0,0 +1,107 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitFlowHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void B(ArmEmitterContext context) + { + OpCodeBImmAl op = (OpCodeBImmAl)context.CurrOp; + + context.Branch(context.GetLabel((ulong)op.Immediate)); + } + + public static void B_Cond(ArmEmitterContext context) + { + OpCodeBImmCond op = (OpCodeBImmCond)context.CurrOp; + + EmitBranch(context, op.Cond); + } + + public static void Bl(ArmEmitterContext context) + { + OpCodeBImmAl op = (OpCodeBImmAl)context.CurrOp; + + context.Copy(GetIntOrZR(context, RegisterAlias.Lr), Const(op.Address + 4)); + + EmitCall(context, (ulong)op.Immediate); + } + + public static void Blr(ArmEmitterContext context) + { + OpCodeBReg op = (OpCodeBReg)context.CurrOp; + + Operand n = context.Copy(GetIntOrZR(context, op.Rn)); + + context.Copy(GetIntOrZR(context, RegisterAlias.Lr), Const(op.Address + 4)); + + EmitVirtualCall(context, n); + } + + public static void Br(ArmEmitterContext context) + { + OpCodeBReg op = (OpCodeBReg)context.CurrOp; + + EmitVirtualJump(context, GetIntOrZR(context, op.Rn), op.Rn == RegisterAlias.Lr); + } + + public static void Cbnz(ArmEmitterContext context) => EmitCb(context, onNotZero: true); + public static void Cbz(ArmEmitterContext context) => EmitCb(context, onNotZero: false); + + private static void EmitCb(ArmEmitterContext context, bool onNotZero) + { + OpCodeBImmCmp op = (OpCodeBImmCmp)context.CurrOp; + + EmitBranch(context, GetIntOrZR(context, op.Rt), onNotZero); + } + + public static void Ret(ArmEmitterContext context) + { + OpCodeBReg op = (OpCodeBReg)context.CurrOp; + + context.Return(GetIntOrZR(context, op.Rn)); + } + + public static void Tbnz(ArmEmitterContext context) => EmitTb(context, onNotZero: true); + public static void Tbz(ArmEmitterContext context) => EmitTb(context, onNotZero: false); + + private static void EmitTb(ArmEmitterContext context, bool onNotZero) + { + OpCodeBImmTest op = (OpCodeBImmTest)context.CurrOp; + + Operand value = context.BitwiseAnd(GetIntOrZR(context, op.Rt), Const(1L << op.Bit)); + + EmitBranch(context, value, onNotZero); + } + + private static void EmitBranch(ArmEmitterContext context, Condition cond) + { + OpCodeBImm op = (OpCodeBImm)context.CurrOp; + + EmitCondBranch(context, context.GetLabel((ulong)op.Immediate), cond); + } + + private static void EmitBranch(ArmEmitterContext context, Operand value, bool onNotZero) + { + OpCodeBImm op = (OpCodeBImm)context.CurrOp; + + Operand lblTarget = context.GetLabel((ulong)op.Immediate); + + if (onNotZero) + { + context.BranchIfTrue(lblTarget, value); + } + else + { + context.BranchIfFalse(lblTarget, value); + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Instructions/InstEmitFlow32.cs b/src/ARMeilleure/Instructions/InstEmitFlow32.cs new file mode 100644 index 00000000..3a7707ee --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitFlow32.cs @@ -0,0 +1,136 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitFlowHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void B(ArmEmitterContext context) + { + IOpCode32BImm op = (IOpCode32BImm)context.CurrOp; + + context.Branch(context.GetLabel((ulong)op.Immediate)); + } + + public static void Bl(ArmEmitterContext context) + { + Blx(context, x: false); + } + + public static void Blx(ArmEmitterContext context) + { + Blx(context, x: true); + } + + private static void Blx(ArmEmitterContext context, bool x) + { + IOpCode32BImm op = (IOpCode32BImm)context.CurrOp; + + uint pc = op.GetPc(); + + bool isThumb = ((OpCode32)context.CurrOp).IsThumb; + + uint currentPc = isThumb + ? pc | 1 + : pc - 4; + + SetIntA32(context, GetBankedRegisterAlias(context.Mode, RegisterAlias.Aarch32Lr), Const(currentPc)); + + // If x is true, then this is a branch with link and exchange. + // In this case we need to swap the mode between Arm <-> Thumb. + if (x) + { + SetFlag(context, PState.TFlag, Const(isThumb ? 0 : 1)); + } + + EmitCall(context, (ulong)op.Immediate); + } + + public static void Blxr(ArmEmitterContext context) + { + IOpCode32BReg op = (IOpCode32BReg)context.CurrOp; + + uint pc = op.GetPc(); + + Operand addr = context.Copy(GetIntA32(context, op.Rm)); + Operand bitOne = context.BitwiseAnd(addr, Const(1)); + + bool isThumb = ((OpCode32)context.CurrOp).IsThumb; + + uint currentPc = isThumb + ? (pc - 2) | 1 + : pc - 4; + + SetIntA32(context, GetBankedRegisterAlias(context.Mode, RegisterAlias.Aarch32Lr), Const(currentPc)); + + SetFlag(context, PState.TFlag, bitOne); + + EmitBxWritePc(context, addr); + } + + public static void Bx(ArmEmitterContext context) + { + IOpCode32BReg op = (IOpCode32BReg)context.CurrOp; + + EmitBxWritePc(context, GetIntA32(context, op.Rm), op.Rm); + } + + public static void Cbnz(ArmEmitterContext context) => EmitCb(context, onNotZero: true); + public static void Cbz(ArmEmitterContext context) => EmitCb(context, onNotZero: false); + + private static void EmitCb(ArmEmitterContext context, bool onNotZero) + { + OpCodeT16BImmCmp op = (OpCodeT16BImmCmp)context.CurrOp; + + Operand value = GetIntA32(context, op.Rn); + Operand lblTarget = context.GetLabel((ulong)op.Immediate); + + if (onNotZero) + { + context.BranchIfTrue(lblTarget, value); + } + else + { + context.BranchIfFalse(lblTarget, value); + } + } + + public static void It(ArmEmitterContext context) + { + OpCodeT16IfThen op = (OpCodeT16IfThen)context.CurrOp; + + context.SetIfThenBlockState(op.IfThenBlockConds); + } + + public static void Tbb(ArmEmitterContext context) => EmitTb(context, halfword: false); + public static void Tbh(ArmEmitterContext context) => EmitTb(context, halfword: true); + + private static void EmitTb(ArmEmitterContext context, bool halfword) + { + OpCodeT32Tb op = (OpCodeT32Tb)context.CurrOp; + + Operand halfwords; + + if (halfword) + { + Operand address = context.Add(GetIntA32(context, op.Rn), context.ShiftLeft(GetIntA32(context, op.Rm), Const(1))); + halfwords = InstEmitMemoryHelper.EmitReadInt(context, address, 1); + } + else + { + Operand address = context.Add(GetIntA32(context, op.Rn), GetIntA32(context, op.Rm)); + halfwords = InstEmitMemoryHelper.EmitReadIntAligned(context, address, 0); + } + + Operand targetAddress = context.Add(Const((int)op.GetPc()), context.ShiftLeft(halfwords, Const(1))); + + EmitVirtualJump(context, targetAddress, isReturn: false); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs b/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs new file mode 100644 index 00000000..6ac32908 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitFlowHelper.cs @@ -0,0 +1,240 @@ +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using ARMeilleure.Translation.PTC; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitFlowHelper + { + public static void EmitCondBranch(ArmEmitterContext context, Operand target, Condition cond) + { + if (cond != Condition.Al) + { + context.BranchIfTrue(target, GetCondTrue(context, cond)); + } + else + { + context.Branch(target); + } + } + + public static Operand GetCondTrue(ArmEmitterContext context, Condition condition) + { + Operand cmpResult = context.TryGetComparisonResult(condition); + + if (cmpResult != default) + { + return cmpResult; + } + + Operand value = Const(1); + + Operand Inverse(Operand val) + { + return context.BitwiseExclusiveOr(val, Const(1)); + } + + switch (condition) + { + case Condition.Eq: + value = GetFlag(PState.ZFlag); + break; + + case Condition.Ne: + value = Inverse(GetFlag(PState.ZFlag)); + break; + + case Condition.GeUn: + value = GetFlag(PState.CFlag); + break; + + case Condition.LtUn: + value = Inverse(GetFlag(PState.CFlag)); + break; + + case Condition.Mi: + value = GetFlag(PState.NFlag); + break; + + case Condition.Pl: + value = Inverse(GetFlag(PState.NFlag)); + break; + + case Condition.Vs: + value = GetFlag(PState.VFlag); + break; + + case Condition.Vc: + value = Inverse(GetFlag(PState.VFlag)); + break; + + case Condition.GtUn: + { + Operand c = GetFlag(PState.CFlag); + Operand z = GetFlag(PState.ZFlag); + + value = context.BitwiseAnd(c, Inverse(z)); + + break; + } + + case Condition.LeUn: + { + Operand c = GetFlag(PState.CFlag); + Operand z = GetFlag(PState.ZFlag); + + value = context.BitwiseOr(Inverse(c), z); + + break; + } + + case Condition.Ge: + { + Operand n = GetFlag(PState.NFlag); + Operand v = GetFlag(PState.VFlag); + + value = context.ICompareEqual(n, v); + + break; + } + + case Condition.Lt: + { + Operand n = GetFlag(PState.NFlag); + Operand v = GetFlag(PState.VFlag); + + value = context.ICompareNotEqual(n, v); + + break; + } + + case Condition.Gt: + { + Operand n = GetFlag(PState.NFlag); + Operand z = GetFlag(PState.ZFlag); + Operand v = GetFlag(PState.VFlag); + + value = context.BitwiseAnd(Inverse(z), context.ICompareEqual(n, v)); + + break; + } + + case Condition.Le: + { + Operand n = GetFlag(PState.NFlag); + Operand z = GetFlag(PState.ZFlag); + Operand v = GetFlag(PState.VFlag); + + value = context.BitwiseOr(z, context.ICompareNotEqual(n, v)); + + break; + } + } + + return value; + } + + public static void EmitCall(ArmEmitterContext context, ulong immediate) + { + bool isRecursive = immediate == context.EntryAddress; + + if (isRecursive) + { + context.Branch(context.GetLabel(immediate)); + } + else + { + EmitTableBranch(context, Const(immediate), isJump: false); + } + } + + public static void EmitVirtualCall(ArmEmitterContext context, Operand target) + { + EmitTableBranch(context, target, isJump: false); + } + + public static void EmitVirtualJump(ArmEmitterContext context, Operand target, bool isReturn) + { + if (isReturn) + { + if (target.Type == OperandType.I32) + { + target = context.ZeroExtend32(OperandType.I64, target); + } + + context.Return(target); + } + else + { + EmitTableBranch(context, target, isJump: true); + } + } + + private static void EmitTableBranch(ArmEmitterContext context, Operand guestAddress, bool isJump) + { + context.StoreToContext(); + + if (guestAddress.Type == OperandType.I32) + { + guestAddress = context.ZeroExtend32(OperandType.I64, guestAddress); + } + + // Store the target guest address into the native context. The stubs uses this address to dispatch into the + // next translation. + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + Operand dispAddressAddr = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset())); + context.Store(dispAddressAddr, guestAddress); + + Operand hostAddress; + + // If address is mapped onto the function table, we can skip the table walk. Otherwise we fallback + // onto the dispatch stub. + if (guestAddress.Kind == OperandKind.Constant && context.FunctionTable.IsValid(guestAddress.Value)) + { + Operand hostAddressAddr = !context.HasPtc ? + Const(ref context.FunctionTable.GetValue(guestAddress.Value)) : + Const(ref context.FunctionTable.GetValue(guestAddress.Value), new Symbol(SymbolType.FunctionTable, guestAddress.Value)); + + hostAddress = context.Load(OperandType.I64, hostAddressAddr); + } + else + { + hostAddress = !context.HasPtc ? + Const((long)context.Stubs.DispatchStub) : + Const((long)context.Stubs.DispatchStub, Ptc.DispatchStubSymbol); + } + + if (isJump) + { + context.Tailcall(hostAddress, nativeContext); + } + else + { + OpCode op = context.CurrOp; + + Operand returnAddress = context.Call(hostAddress, OperandType.I64, nativeContext); + + context.LoadFromContext(); + + // Note: The return value of a translated function is always an Int64 with the address execution has + // returned to. We expect this address to be immediately after the current instruction, if it isn't we + // keep returning until we reach the dispatcher. + Operand nextAddr = Const((long)op.Address + op.OpCodeSizeInBytes); + + // Try to continue within this block. + // If the return address isn't to our next instruction, we need to return so the JIT can figure out + // what to do. + Operand lblContinue = context.GetLabel(nextAddr.Value); + context.BranchIf(lblContinue, returnAddress, nextAddr, Comparison.Equal, BasicBlockFrequency.Cold); + + context.Return(returnAddress); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitHash.cs b/src/ARMeilleure/Instructions/InstEmitHash.cs new file mode 100644 index 00000000..82b3e353 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitHash.cs @@ -0,0 +1,69 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHashHelper; +using static ARMeilleure.Instructions.InstEmitHelper; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + private const int ByteSizeLog2 = 0; + private const int HWordSizeLog2 = 1; + private const int WordSizeLog2 = 2; + private const int DWordSizeLog2 = 3; + + public static void Crc32b(ArmEmitterContext context) + { + EmitCrc32Call(context, ByteSizeLog2, false); + } + + public static void Crc32h(ArmEmitterContext context) + { + EmitCrc32Call(context, HWordSizeLog2, false); + } + + public static void Crc32w(ArmEmitterContext context) + { + EmitCrc32Call(context, WordSizeLog2, false); + } + + public static void Crc32x(ArmEmitterContext context) + { + EmitCrc32Call(context, DWordSizeLog2, false); + } + + public static void Crc32cb(ArmEmitterContext context) + { + EmitCrc32Call(context, ByteSizeLog2, true); + } + + public static void Crc32ch(ArmEmitterContext context) + { + EmitCrc32Call(context, HWordSizeLog2, true); + } + + public static void Crc32cw(ArmEmitterContext context) + { + EmitCrc32Call(context, WordSizeLog2, true); + } + + public static void Crc32cx(ArmEmitterContext context) + { + EmitCrc32Call(context, DWordSizeLog2, true); + } + + private static void EmitCrc32Call(ArmEmitterContext context, int size, bool c) + { + OpCodeAluBinary op = (OpCodeAluBinary)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + Operand m = GetIntOrZR(context, op.Rm); + + Operand d = EmitCrc32(context, n, m, size, c); + + SetIntOrZR(context, op.Rd, d); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitHash32.cs b/src/ARMeilleure/Instructions/InstEmitHash32.cs new file mode 100644 index 00000000..5d39f8af --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitHash32.cs @@ -0,0 +1,53 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using static ARMeilleure.Instructions.InstEmitHashHelper; +using static ARMeilleure.Instructions.InstEmitHelper; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void Crc32b(ArmEmitterContext context) + { + EmitCrc32Call(context, ByteSizeLog2, false); + } + + public static void Crc32h(ArmEmitterContext context) + { + EmitCrc32Call(context, HWordSizeLog2, false); + } + + public static void Crc32w(ArmEmitterContext context) + { + EmitCrc32Call(context, WordSizeLog2, false); + } + + public static void Crc32cb(ArmEmitterContext context) + { + EmitCrc32Call(context, ByteSizeLog2, true); + } + + public static void Crc32ch(ArmEmitterContext context) + { + EmitCrc32Call(context, HWordSizeLog2, true); + } + + public static void Crc32cw(ArmEmitterContext context) + { + EmitCrc32Call(context, WordSizeLog2, true); + } + + private static void EmitCrc32Call(ArmEmitterContext context, int size, bool c) + { + IOpCode32AluReg op = (IOpCode32AluReg)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + + Operand d = EmitCrc32(context, n, m, size, c); + + EmitAluStore(context, d); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitHashHelper.cs b/src/ARMeilleure/Instructions/InstEmitHashHelper.cs new file mode 100644 index 00000000..55a03a4f --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitHashHelper.cs @@ -0,0 +1,118 @@ +// https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf + +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitHashHelper + { + public const uint Crc32RevPoly = 0xedb88320; + public const uint Crc32cRevPoly = 0x82f63b78; + + public static Operand EmitCrc32(ArmEmitterContext context, Operand crc, Operand value, int size, bool castagnoli) + { + Debug.Assert(crc.Type.IsInteger() && value.Type.IsInteger()); + Debug.Assert(size >= 0 && size < 4); + Debug.Assert((size < 3) || (value.Type == OperandType.I64)); + + if (castagnoli && Optimizations.UseSse42) + { + // The CRC32 instruction does not have an immediate variant, so ensure both inputs are in registers. + value = (value.Kind == OperandKind.Constant) ? context.Copy(value) : value; + crc = (crc.Kind == OperandKind.Constant) ? context.Copy(crc) : crc; + + Intrinsic op = size switch + { + 0 => Intrinsic.X86Crc32_8, + 1 => Intrinsic.X86Crc32_16, + _ => Intrinsic.X86Crc32, + }; + + return (size == 3) ? context.ConvertI64ToI32(context.AddIntrinsicLong(op, crc, value)) : context.AddIntrinsicInt(op, crc, value); + } + else if (Optimizations.UsePclmulqdq) + { + return size switch + { + 3 => EmitCrc32Optimized64(context, crc, value, castagnoli), + _ => EmitCrc32Optimized(context, crc, value, castagnoli, size), + }; + } + else + { + string name = (size, castagnoli) switch + { + (0, false) => nameof(SoftFallback.Crc32b), + (1, false) => nameof(SoftFallback.Crc32h), + (2, false) => nameof(SoftFallback.Crc32w), + (3, false) => nameof(SoftFallback.Crc32x), + (0, true) => nameof(SoftFallback.Crc32cb), + (1, true) => nameof(SoftFallback.Crc32ch), + (2, true) => nameof(SoftFallback.Crc32cw), + (3, true) => nameof(SoftFallback.Crc32cx), + _ => throw new ArgumentOutOfRangeException(nameof(size)) + }; + + return context.Call(typeof(SoftFallback).GetMethod(name), crc, value); + } + } + + private static Operand EmitCrc32Optimized(ArmEmitterContext context, Operand crc, Operand data, bool castagnoli, int size) + { + long mu = castagnoli ? 0x0DEA713F1 : 0x1F7011641; // mu' = floor(x^64/P(x))' + long polynomial = castagnoli ? 0x105EC76F0 : 0x1DB710641; // P'(x) << 1 + + crc = context.VectorInsert(context.VectorZero(), crc, 0); + + switch (size) + { + case 0: data = context.VectorInsert8(context.VectorZero(), data, 0); break; + case 1: data = context.VectorInsert16(context.VectorZero(), data, 0); break; + case 2: data = context.VectorInsert(context.VectorZero(), data, 0); break; + } + + int bitsize = 8 << size; + + Operand tmp = context.AddIntrinsic(Intrinsic.X86Pxor, crc, data); + tmp = context.AddIntrinsic(Intrinsic.X86Psllq, tmp, Const(64 - bitsize)); + tmp = context.AddIntrinsic(Intrinsic.X86Pclmulqdq, tmp, X86GetScalar(context, mu), Const(0)); + tmp = context.AddIntrinsic(Intrinsic.X86Pclmulqdq, tmp, X86GetScalar(context, polynomial), Const(0)); + + if (bitsize < 32) + { + crc = context.AddIntrinsic(Intrinsic.X86Pslldq, crc, Const((64 - bitsize) / 8)); + tmp = context.AddIntrinsic(Intrinsic.X86Pxor, tmp, crc); + } + + return context.VectorExtract(OperandType.I32, tmp, 2); + } + + private static Operand EmitCrc32Optimized64(ArmEmitterContext context, Operand crc, Operand data, bool castagnoli) + { + long mu = castagnoli ? 0x0DEA713F1 : 0x1F7011641; // mu' = floor(x^64/P(x))' + long polynomial = castagnoli ? 0x105EC76F0 : 0x1DB710641; // P'(x) << 1 + + crc = context.VectorInsert(context.VectorZero(), crc, 0); + data = context.VectorInsert(context.VectorZero(), data, 0); + + Operand tmp = context.AddIntrinsic(Intrinsic.X86Pxor, crc, data); + Operand res = context.AddIntrinsic(Intrinsic.X86Pslldq, tmp, Const(4)); + + tmp = context.AddIntrinsic(Intrinsic.X86Pclmulqdq, res, X86GetScalar(context, mu), Const(0)); + tmp = context.AddIntrinsic(Intrinsic.X86Pclmulqdq, tmp, X86GetScalar(context, polynomial), Const(0)); + + tmp = context.AddIntrinsic(Intrinsic.X86Pxor, tmp, res); + tmp = context.AddIntrinsic(Intrinsic.X86Psllq, tmp, Const(32)); + + tmp = context.AddIntrinsic(Intrinsic.X86Pclmulqdq, tmp, X86GetScalar(context, mu), Const(1)); + tmp = context.AddIntrinsic(Intrinsic.X86Pclmulqdq, tmp, X86GetScalar(context, polynomial), Const(0)); + + return context.VectorExtract(OperandType.I32, tmp, 2); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitHelper.cs b/src/ARMeilleure/Instructions/InstEmitHelper.cs new file mode 100644 index 00000000..a22bb3fb --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitHelper.cs @@ -0,0 +1,264 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; + +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitHelper + { + public static Operand GetExtendedM(ArmEmitterContext context, int rm, IntType type) + { + Operand value = GetIntOrZR(context, rm); + + switch (type) + { + case IntType.UInt8: value = context.ZeroExtend8 (value.Type, value); break; + case IntType.UInt16: value = context.ZeroExtend16(value.Type, value); break; + case IntType.UInt32: value = context.ZeroExtend32(value.Type, value); break; + + case IntType.Int8: value = context.SignExtend8 (value.Type, value); break; + case IntType.Int16: value = context.SignExtend16(value.Type, value); break; + case IntType.Int32: value = context.SignExtend32(value.Type, value); break; + } + + return value; + } + + public static Operand GetIntA32(ArmEmitterContext context, int regIndex) + { + if (regIndex == RegisterAlias.Aarch32Pc) + { + OpCode32 op = (OpCode32)context.CurrOp; + + return Const((int)op.GetPc()); + } + else + { + return Register(GetRegisterAlias(context.Mode, regIndex), RegisterType.Integer, OperandType.I32); + } + } + + public static Operand GetIntA32AlignedPC(ArmEmitterContext context, int regIndex) + { + if (regIndex == RegisterAlias.Aarch32Pc) + { + OpCode32 op = (OpCode32)context.CurrOp; + + return Const((int)(op.GetPc() & 0xfffffffc)); + } + else + { + return Register(GetRegisterAlias(context.Mode, regIndex), RegisterType.Integer, OperandType.I32); + } + } + + public static Operand GetVecA32(int regIndex) + { + return Register(regIndex, RegisterType.Vector, OperandType.V128); + } + + public static void SetIntA32(ArmEmitterContext context, int regIndex, Operand value) + { + if (regIndex == RegisterAlias.Aarch32Pc) + { + if (!IsA32Return(context)) + { + context.StoreToContext(); + } + + EmitBxWritePc(context, value); + } + else + { + if (value.Type == OperandType.I64) + { + value = context.ConvertI64ToI32(value); + } + Operand reg = Register(GetRegisterAlias(context.Mode, regIndex), RegisterType.Integer, OperandType.I32); + + context.Copy(reg, value); + } + } + + public static int GetRegisterAlias(Aarch32Mode mode, int regIndex) + { + // Only registers >= 8 are banked, + // with registers in the range [8, 12] being + // banked for the FIQ mode, and registers + // 13 and 14 being banked for all modes. + if ((uint)regIndex < 8) + { + return regIndex; + } + + return GetBankedRegisterAlias(mode, regIndex); + } + + public static int GetBankedRegisterAlias(Aarch32Mode mode, int regIndex) + { + switch (regIndex) + { + case 8: return mode == Aarch32Mode.Fiq + ? RegisterAlias.R8Fiq + : RegisterAlias.R8Usr; + + case 9: return mode == Aarch32Mode.Fiq + ? RegisterAlias.R9Fiq + : RegisterAlias.R9Usr; + + case 10: return mode == Aarch32Mode.Fiq + ? RegisterAlias.R10Fiq + : RegisterAlias.R10Usr; + + case 11: return mode == Aarch32Mode.Fiq + ? RegisterAlias.R11Fiq + : RegisterAlias.R11Usr; + + case 12: return mode == Aarch32Mode.Fiq + ? RegisterAlias.R12Fiq + : RegisterAlias.R12Usr; + + case 13: + switch (mode) + { + case Aarch32Mode.User: + case Aarch32Mode.System: return RegisterAlias.SpUsr; + case Aarch32Mode.Fiq: return RegisterAlias.SpFiq; + case Aarch32Mode.Irq: return RegisterAlias.SpIrq; + case Aarch32Mode.Supervisor: return RegisterAlias.SpSvc; + case Aarch32Mode.Abort: return RegisterAlias.SpAbt; + case Aarch32Mode.Hypervisor: return RegisterAlias.SpHyp; + case Aarch32Mode.Undefined: return RegisterAlias.SpUnd; + + default: throw new ArgumentException(nameof(mode)); + } + + case 14: + switch (mode) + { + case Aarch32Mode.User: + case Aarch32Mode.Hypervisor: + case Aarch32Mode.System: return RegisterAlias.LrUsr; + case Aarch32Mode.Fiq: return RegisterAlias.LrFiq; + case Aarch32Mode.Irq: return RegisterAlias.LrIrq; + case Aarch32Mode.Supervisor: return RegisterAlias.LrSvc; + case Aarch32Mode.Abort: return RegisterAlias.LrAbt; + case Aarch32Mode.Undefined: return RegisterAlias.LrUnd; + + default: throw new ArgumentException(nameof(mode)); + } + + default: throw new ArgumentOutOfRangeException(nameof(regIndex)); + } + } + + public static bool IsA32Return(ArmEmitterContext context) + { + switch (context.CurrOp) + { + case IOpCode32MemMult op: + return true; // Setting PC using LDM is nearly always a return. + case OpCode32AluRsImm op: + return op.Rm == RegisterAlias.Aarch32Lr; + case OpCode32AluRsReg op: + return op.Rm == RegisterAlias.Aarch32Lr; + case OpCode32AluReg op: + return op.Rm == RegisterAlias.Aarch32Lr; + case OpCode32Mem op: + return op.Rn == RegisterAlias.Aarch32Sp && op.WBack && !op.Index; // Setting PC to an address stored on the stack is nearly always a return. + } + return false; + } + + public static void EmitBxWritePc(ArmEmitterContext context, Operand pc, int sourceRegister = 0) + { + bool isReturn = sourceRegister == RegisterAlias.Aarch32Lr || IsA32Return(context); + Operand mode = context.BitwiseAnd(pc, Const(1)); + + SetFlag(context, PState.TFlag, mode); + + Operand addr = context.ConditionalSelect(mode, context.BitwiseAnd(pc, Const(~1)), context.BitwiseAnd(pc, Const(~3))); + + InstEmitFlowHelper.EmitVirtualJump(context, addr, isReturn); + } + + public static Operand GetIntOrZR(ArmEmitterContext context, int regIndex) + { + if (regIndex == RegisterConsts.ZeroIndex) + { + OperandType type = context.CurrOp.GetOperandType(); + + return type == OperandType.I32 ? Const(0) : Const(0L); + } + else + { + return GetIntOrSP(context, regIndex); + } + } + + public static void SetIntOrZR(ArmEmitterContext context, int regIndex, Operand value) + { + if (regIndex == RegisterConsts.ZeroIndex) + { + return; + } + + SetIntOrSP(context, regIndex, value); + } + + public static Operand GetIntOrSP(ArmEmitterContext context, int regIndex) + { + Operand value = Register(regIndex, RegisterType.Integer, OperandType.I64); + + if (context.CurrOp.RegisterSize == RegisterSize.Int32) + { + value = context.ConvertI64ToI32(value); + } + + return value; + } + + public static void SetIntOrSP(ArmEmitterContext context, int regIndex, Operand value) + { + Operand reg = Register(regIndex, RegisterType.Integer, OperandType.I64); + + if (value.Type == OperandType.I32) + { + value = context.ZeroExtend32(OperandType.I64, value); + } + + context.Copy(reg, value); + } + + public static Operand GetVec(int regIndex) + { + return Register(regIndex, RegisterType.Vector, OperandType.V128); + } + + public static Operand GetFlag(PState stateFlag) + { + return Register((int)stateFlag, RegisterType.Flag, OperandType.I32); + } + + public static Operand GetFpFlag(FPState stateFlag) + { + return Register((int)stateFlag, RegisterType.FpFlag, OperandType.I32); + } + + public static void SetFlag(ArmEmitterContext context, PState stateFlag, Operand value) + { + context.Copy(GetFlag(stateFlag), value); + + context.MarkFlagSet(stateFlag); + } + + public static void SetFpFlag(ArmEmitterContext context, FPState stateFlag, Operand value) + { + context.Copy(GetFpFlag(stateFlag), value); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitMemory.cs b/src/ARMeilleure/Instructions/InstEmitMemory.cs new file mode 100644 index 00000000..7baed14c --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMemory.cs @@ -0,0 +1,184 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitMemoryHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Adr(ArmEmitterContext context) + { + OpCodeAdr op = (OpCodeAdr)context.CurrOp; + + SetIntOrZR(context, op.Rd, Const(op.Address + (ulong)op.Immediate)); + } + + public static void Adrp(ArmEmitterContext context) + { + OpCodeAdr op = (OpCodeAdr)context.CurrOp; + + ulong address = (op.Address & ~0xfffUL) + ((ulong)op.Immediate << 12); + + SetIntOrZR(context, op.Rd, Const(address)); + } + + public static void Ldr(ArmEmitterContext context) => EmitLdr(context, signed: false); + public static void Ldrs(ArmEmitterContext context) => EmitLdr(context, signed: true); + + private static void EmitLdr(ArmEmitterContext context, bool signed) + { + OpCodeMem op = (OpCodeMem)context.CurrOp; + + Operand address = GetAddress(context); + + if (signed && op.Extend64) + { + EmitLoadSx64(context, address, op.Rt, op.Size); + } + else if (signed) + { + EmitLoadSx32(context, address, op.Rt, op.Size); + } + else + { + EmitLoadZx(context, address, op.Rt, op.Size); + } + + EmitWBackIfNeeded(context, address); + } + + public static void Ldr_Literal(ArmEmitterContext context) + { + IOpCodeLit op = (IOpCodeLit)context.CurrOp; + + if (op.Prefetch) + { + return; + } + + if (op.Signed) + { + EmitLoadSx64(context, Const(op.Immediate), op.Rt, op.Size); + } + else + { + EmitLoadZx(context, Const(op.Immediate), op.Rt, op.Size); + } + } + + public static void Ldp(ArmEmitterContext context) + { + OpCodeMemPair op = (OpCodeMemPair)context.CurrOp; + + void EmitLoad(int rt, Operand ldAddr) + { + if (op.Extend64) + { + EmitLoadSx64(context, ldAddr, rt, op.Size); + } + else + { + EmitLoadZx(context, ldAddr, rt, op.Size); + } + } + + Operand address = GetAddress(context); + Operand address2 = GetAddress(context, 1L << op.Size); + + EmitLoad(op.Rt, address); + EmitLoad(op.Rt2, address2); + + EmitWBackIfNeeded(context, address); + } + + public static void Str(ArmEmitterContext context) + { + OpCodeMem op = (OpCodeMem)context.CurrOp; + + Operand address = GetAddress(context); + + EmitStore(context, address, op.Rt, op.Size); + + EmitWBackIfNeeded(context, address); + } + + public static void Stp(ArmEmitterContext context) + { + OpCodeMemPair op = (OpCodeMemPair)context.CurrOp; + + Operand address = GetAddress(context); + Operand address2 = GetAddress(context, 1L << op.Size); + + EmitStore(context, address, op.Rt, op.Size); + EmitStore(context, address2, op.Rt2, op.Size); + + EmitWBackIfNeeded(context, address); + } + + private static Operand GetAddress(ArmEmitterContext context, long addend = 0) + { + Operand address = default; + + switch (context.CurrOp) + { + case OpCodeMemImm op: + { + address = context.Copy(GetIntOrSP(context, op.Rn)); + + // Pre-indexing. + if (!op.PostIdx) + { + address = context.Add(address, Const(op.Immediate + addend)); + } + else if (addend != 0) + { + address = context.Add(address, Const(addend)); + } + + break; + } + + case OpCodeMemReg op: + { + Operand n = GetIntOrSP(context, op.Rn); + + Operand m = GetExtendedM(context, op.Rm, op.IntType); + + if (op.Shift) + { + m = context.ShiftLeft(m, Const(op.Size)); + } + + address = context.Add(n, m); + + if (addend != 0) + { + address = context.Add(address, Const(addend)); + } + + break; + } + } + + return address; + } + + private static void EmitWBackIfNeeded(ArmEmitterContext context, Operand address) + { + // Check whenever the current OpCode has post-indexed write back, if so write it. + if (context.CurrOp is OpCodeMemImm op && op.WBack) + { + if (op.PostIdx) + { + address = context.Add(address, Const(op.Immediate)); + } + + SetIntOrSP(context, op.Rn, address); + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Instructions/InstEmitMemory32.cs b/src/ARMeilleure/Instructions/InstEmitMemory32.cs new file mode 100644 index 00000000..17ec97aa --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMemory32.cs @@ -0,0 +1,265 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitMemoryHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + private const int ByteSizeLog2 = 0; + private const int HWordSizeLog2 = 1; + private const int WordSizeLog2 = 2; + private const int DWordSizeLog2 = 3; + + [Flags] + enum AccessType + { + Store = 0, + Signed = 1, + Load = 2, + Ordered = 4, + Exclusive = 8, + + LoadZx = Load, + LoadSx = Load | Signed, + } + + public static void Ldm(ArmEmitterContext context) + { + IOpCode32MemMult op = (IOpCode32MemMult)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + + Operand baseAddress = context.Add(n, Const(op.Offset)); + + bool writesToPc = (op.RegisterMask & (1 << RegisterAlias.Aarch32Pc)) != 0; + + bool writeBack = op.PostOffset != 0 && (op.Rn != RegisterAlias.Aarch32Pc || !writesToPc); + + if (writeBack) + { + SetIntA32(context, op.Rn, context.Add(n, Const(op.PostOffset))); + } + + int mask = op.RegisterMask; + int offset = 0; + + for (int register = 0; mask != 0; mask >>= 1, register++) + { + if ((mask & 1) != 0) + { + Operand address = context.Add(baseAddress, Const(offset)); + + EmitLoadZx(context, address, register, WordSizeLog2); + + offset += 4; + } + } + } + + public static void Ldr(ArmEmitterContext context) + { + EmitLoadOrStore(context, WordSizeLog2, AccessType.LoadZx); + } + + public static void Ldrb(ArmEmitterContext context) + { + EmitLoadOrStore(context, ByteSizeLog2, AccessType.LoadZx); + } + + public static void Ldrd(ArmEmitterContext context) + { + EmitLoadOrStore(context, DWordSizeLog2, AccessType.LoadZx); + } + + public static void Ldrh(ArmEmitterContext context) + { + EmitLoadOrStore(context, HWordSizeLog2, AccessType.LoadZx); + } + + public static void Ldrsb(ArmEmitterContext context) + { + EmitLoadOrStore(context, ByteSizeLog2, AccessType.LoadSx); + } + + public static void Ldrsh(ArmEmitterContext context) + { + EmitLoadOrStore(context, HWordSizeLog2, AccessType.LoadSx); + } + + public static void Stm(ArmEmitterContext context) + { + IOpCode32MemMult op = (IOpCode32MemMult)context.CurrOp; + + Operand n = context.Copy(GetIntA32(context, op.Rn)); + + Operand baseAddress = context.Add(n, Const(op.Offset)); + + int mask = op.RegisterMask; + int offset = 0; + + for (int register = 0; mask != 0; mask >>= 1, register++) + { + if ((mask & 1) != 0) + { + Operand address = context.Add(baseAddress, Const(offset)); + + EmitStore(context, address, register, WordSizeLog2); + + // Note: If Rn is also specified on the register list, + // and Rn is the first register on this list, then the + // value that is written to memory is the unmodified value, + // before the write back. If it is on the list, but it's + // not the first one, then the value written to memory + // varies between CPUs. + if (offset == 0 && op.PostOffset != 0) + { + // Emit write back after the first write. + SetIntA32(context, op.Rn, context.Add(n, Const(op.PostOffset))); + } + + offset += 4; + } + } + } + + public static void Str(ArmEmitterContext context) + { + EmitLoadOrStore(context, WordSizeLog2, AccessType.Store); + } + + public static void Strb(ArmEmitterContext context) + { + EmitLoadOrStore(context, ByteSizeLog2, AccessType.Store); + } + + public static void Strd(ArmEmitterContext context) + { + EmitLoadOrStore(context, DWordSizeLog2, AccessType.Store); + } + + public static void Strh(ArmEmitterContext context) + { + EmitLoadOrStore(context, HWordSizeLog2, AccessType.Store); + } + + private static void EmitLoadOrStore(ArmEmitterContext context, int size, AccessType accType) + { + IOpCode32Mem op = (IOpCode32Mem)context.CurrOp; + + Operand n = context.Copy(GetIntA32AlignedPC(context, op.Rn)); + Operand m = GetMemM(context, setCarry: false); + + Operand temp = default; + + if (op.Index || op.WBack) + { + temp = op.Add + ? context.Add (n, m) + : context.Subtract(n, m); + } + + if (op.WBack) + { + SetIntA32(context, op.Rn, temp); + } + + Operand address; + + if (op.Index) + { + address = temp; + } + else + { + address = n; + } + + if ((accType & AccessType.Load) != 0) + { + void Load(int rt, int offs, int loadSize) + { + Operand addr = context.Add(address, Const(offs)); + + if ((accType & AccessType.Signed) != 0) + { + EmitLoadSx32(context, addr, rt, loadSize); + } + else + { + EmitLoadZx(context, addr, rt, loadSize); + } + } + + if (size == DWordSizeLog2) + { + Operand lblBigEndian = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBigEndian, GetFlag(PState.EFlag)); + + Load(op.Rt, 0, WordSizeLog2); + Load(op.Rt2, 4, WordSizeLog2); + + context.Branch(lblEnd); + + context.MarkLabel(lblBigEndian); + + Load(op.Rt2, 0, WordSizeLog2); + Load(op.Rt, 4, WordSizeLog2); + + context.MarkLabel(lblEnd); + } + else + { + Load(op.Rt, 0, size); + } + } + else + { + void Store(int rt, int offs, int storeSize) + { + Operand addr = context.Add(address, Const(offs)); + + EmitStore(context, addr, rt, storeSize); + } + + if (size == DWordSizeLog2) + { + Operand lblBigEndian = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBigEndian, GetFlag(PState.EFlag)); + + Store(op.Rt, 0, WordSizeLog2); + Store(op.Rt2, 4, WordSizeLog2); + + context.Branch(lblEnd); + + context.MarkLabel(lblBigEndian); + + Store(op.Rt2, 0, WordSizeLog2); + Store(op.Rt, 4, WordSizeLog2); + + context.MarkLabel(lblEnd); + } + else + { + Store(op.Rt, 0, size); + } + } + } + + public static void Adr(ArmEmitterContext context) + { + IOpCode32Adr op = (IOpCode32Adr)context.CurrOp; + SetIntA32(context, op.Rd, Const(op.Immediate)); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Instructions/InstEmitMemoryEx.cs b/src/ARMeilleure/Instructions/InstEmitMemoryEx.cs new file mode 100644 index 00000000..c7ed01e3 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMemoryEx.cs @@ -0,0 +1,178 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitMemoryExHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + [Flags] + private enum AccessType + { + None = 0, + Ordered = 1, + Exclusive = 2, + OrderedEx = Ordered | Exclusive + } + + public static void Clrex(ArmEmitterContext context) + { + EmitClearExclusive(context); + } + + public static void Csdb(ArmEmitterContext context) + { + // Execute as no-op. + } + + public static void Dmb(ArmEmitterContext context) => EmitBarrier(context); + public static void Dsb(ArmEmitterContext context) => EmitBarrier(context); + + public static void Ldar(ArmEmitterContext context) => EmitLdr(context, AccessType.Ordered); + public static void Ldaxr(ArmEmitterContext context) => EmitLdr(context, AccessType.OrderedEx); + public static void Ldxr(ArmEmitterContext context) => EmitLdr(context, AccessType.Exclusive); + public static void Ldxp(ArmEmitterContext context) => EmitLdp(context, AccessType.Exclusive); + public static void Ldaxp(ArmEmitterContext context) => EmitLdp(context, AccessType.OrderedEx); + + private static void EmitLdr(ArmEmitterContext context, AccessType accType) + { + EmitLoadEx(context, accType, pair: false); + } + + private static void EmitLdp(ArmEmitterContext context, AccessType accType) + { + EmitLoadEx(context, accType, pair: true); + } + + private static void EmitLoadEx(ArmEmitterContext context, AccessType accType, bool pair) + { + OpCodeMemEx op = (OpCodeMemEx)context.CurrOp; + + bool ordered = (accType & AccessType.Ordered) != 0; + bool exclusive = (accType & AccessType.Exclusive) != 0; + + if (ordered) + { + EmitBarrier(context); + } + + Operand address = context.Copy(GetIntOrSP(context, op.Rn)); + + if (pair) + { + // Exclusive loads should be atomic. For pairwise loads, we need to + // read all the data at once. For a 32-bits pairwise load, we do a + // simple 64-bits load, for a 128-bits load, we need to call a special + // method to read 128-bits atomically. + if (op.Size == 2) + { + Operand value = EmitLoadExclusive(context, address, exclusive, 3); + + Operand valueLow = context.ConvertI64ToI32(value); + + valueLow = context.ZeroExtend32(OperandType.I64, valueLow); + + Operand valueHigh = context.ShiftRightUI(value, Const(32)); + + SetIntOrZR(context, op.Rt, valueLow); + SetIntOrZR(context, op.Rt2, valueHigh); + } + else if (op.Size == 3) + { + Operand value = EmitLoadExclusive(context, address, exclusive, 4); + + Operand valueLow = context.VectorExtract(OperandType.I64, value, 0); + Operand valueHigh = context.VectorExtract(OperandType.I64, value, 1); + + SetIntOrZR(context, op.Rt, valueLow); + SetIntOrZR(context, op.Rt2, valueHigh); + } + else + { + throw new InvalidOperationException($"Invalid load size of {1 << op.Size} bytes."); + } + } + else + { + // 8, 16, 32 or 64-bits (non-pairwise) load. + Operand value = EmitLoadExclusive(context, address, exclusive, op.Size); + + SetIntOrZR(context, op.Rt, value); + } + } + + public static void Prfm(ArmEmitterContext context) + { + // Memory Prefetch, execute as no-op. + } + + public static void Stlr(ArmEmitterContext context) => EmitStr(context, AccessType.Ordered); + public static void Stlxr(ArmEmitterContext context) => EmitStr(context, AccessType.OrderedEx); + public static void Stxr(ArmEmitterContext context) => EmitStr(context, AccessType.Exclusive); + public static void Stxp(ArmEmitterContext context) => EmitStp(context, AccessType.Exclusive); + public static void Stlxp(ArmEmitterContext context) => EmitStp(context, AccessType.OrderedEx); + + private static void EmitStr(ArmEmitterContext context, AccessType accType) + { + EmitStoreEx(context, accType, pair: false); + } + + private static void EmitStp(ArmEmitterContext context, AccessType accType) + { + EmitStoreEx(context, accType, pair: true); + } + + private static void EmitStoreEx(ArmEmitterContext context, AccessType accType, bool pair) + { + OpCodeMemEx op = (OpCodeMemEx)context.CurrOp; + + bool ordered = (accType & AccessType.Ordered) != 0; + bool exclusive = (accType & AccessType.Exclusive) != 0; + + Operand address = context.Copy(GetIntOrSP(context, op.Rn)); + + Operand t = GetIntOrZR(context, op.Rt); + + if (pair) + { + Debug.Assert(op.Size == 2 || op.Size == 3, "Invalid size for pairwise store."); + + Operand t2 = GetIntOrZR(context, op.Rt2); + + Operand value; + + if (op.Size == 2) + { + value = context.BitwiseOr(t, context.ShiftLeft(t2, Const(32))); + } + else /* if (op.Size == 3) */ + { + value = context.VectorInsert(context.VectorZero(), t, 0); + value = context.VectorInsert(value, t2, 1); + } + + EmitStoreExclusive(context, address, value, exclusive, op.Size + 1, op.Rs, a32: false); + } + else + { + EmitStoreExclusive(context, address, t, exclusive, op.Size, op.Rs, a32: false); + } + + if (ordered) + { + EmitBarrier(context); + } + } + + private static void EmitBarrier(ArmEmitterContext context) + { + context.MemoryBarrier(); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Instructions/InstEmitMemoryEx32.cs b/src/ARMeilleure/Instructions/InstEmitMemoryEx32.cs new file mode 100644 index 00000000..c0b6fc39 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMemoryEx32.cs @@ -0,0 +1,237 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitMemoryExHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void Clrex(ArmEmitterContext context) + { + EmitClearExclusive(context); + } + + public static void Csdb(ArmEmitterContext context) + { + // Execute as no-op. + } + + public static void Dmb(ArmEmitterContext context) => EmitBarrier(context); + + public static void Dsb(ArmEmitterContext context) => EmitBarrier(context); + + public static void Ldrex(ArmEmitterContext context) + { + EmitExLoadOrStore(context, WordSizeLog2, AccessType.LoadZx | AccessType.Exclusive); + } + + public static void Ldrexb(ArmEmitterContext context) + { + EmitExLoadOrStore(context, ByteSizeLog2, AccessType.LoadZx | AccessType.Exclusive); + } + + public static void Ldrexd(ArmEmitterContext context) + { + EmitExLoadOrStore(context, DWordSizeLog2, AccessType.LoadZx | AccessType.Exclusive); + } + + public static void Ldrexh(ArmEmitterContext context) + { + EmitExLoadOrStore(context, HWordSizeLog2, AccessType.LoadZx | AccessType.Exclusive); + } + + public static void Lda(ArmEmitterContext context) + { + EmitExLoadOrStore(context, WordSizeLog2, AccessType.LoadZx | AccessType.Ordered); + } + + public static void Ldab(ArmEmitterContext context) + { + EmitExLoadOrStore(context, ByteSizeLog2, AccessType.LoadZx | AccessType.Ordered); + } + + public static void Ldaex(ArmEmitterContext context) + { + EmitExLoadOrStore(context, WordSizeLog2, AccessType.LoadZx | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Ldaexb(ArmEmitterContext context) + { + EmitExLoadOrStore(context, ByteSizeLog2, AccessType.LoadZx | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Ldaexd(ArmEmitterContext context) + { + EmitExLoadOrStore(context, DWordSizeLog2, AccessType.LoadZx | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Ldaexh(ArmEmitterContext context) + { + EmitExLoadOrStore(context, HWordSizeLog2, AccessType.LoadZx | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Ldah(ArmEmitterContext context) + { + EmitExLoadOrStore(context, HWordSizeLog2, AccessType.LoadZx | AccessType.Ordered); + } + + // Stores. + + public static void Strex(ArmEmitterContext context) + { + EmitExLoadOrStore(context, WordSizeLog2, AccessType.Store | AccessType.Exclusive); + } + + public static void Strexb(ArmEmitterContext context) + { + EmitExLoadOrStore(context, ByteSizeLog2, AccessType.Store | AccessType.Exclusive); + } + + public static void Strexd(ArmEmitterContext context) + { + EmitExLoadOrStore(context, DWordSizeLog2, AccessType.Store | AccessType.Exclusive); + } + + public static void Strexh(ArmEmitterContext context) + { + EmitExLoadOrStore(context, HWordSizeLog2, AccessType.Store | AccessType.Exclusive); + } + + public static void Stl(ArmEmitterContext context) + { + EmitExLoadOrStore(context, WordSizeLog2, AccessType.Store | AccessType.Ordered); + } + + public static void Stlb(ArmEmitterContext context) + { + EmitExLoadOrStore(context, ByteSizeLog2, AccessType.Store | AccessType.Ordered); + } + + public static void Stlex(ArmEmitterContext context) + { + EmitExLoadOrStore(context, WordSizeLog2, AccessType.Store | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Stlexb(ArmEmitterContext context) + { + EmitExLoadOrStore(context, ByteSizeLog2, AccessType.Store | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Stlexd(ArmEmitterContext context) + { + EmitExLoadOrStore(context, DWordSizeLog2, AccessType.Store | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Stlexh(ArmEmitterContext context) + { + EmitExLoadOrStore(context, HWordSizeLog2, AccessType.Store | AccessType.Exclusive | AccessType.Ordered); + } + + public static void Stlh(ArmEmitterContext context) + { + EmitExLoadOrStore(context, HWordSizeLog2, AccessType.Store | AccessType.Ordered); + } + + private static void EmitExLoadOrStore(ArmEmitterContext context, int size, AccessType accType) + { + IOpCode32MemEx op = (IOpCode32MemEx)context.CurrOp; + + Operand address = context.Copy(GetIntA32(context, op.Rn)); + + var exclusive = (accType & AccessType.Exclusive) != 0; + var ordered = (accType & AccessType.Ordered) != 0; + + if ((accType & AccessType.Load) != 0) + { + if (ordered) + { + EmitBarrier(context); + } + + if (size == DWordSizeLog2) + { + // Keep loads atomic - make the call to get the whole region and then decompose it into parts + // for the registers. + + Operand value = EmitLoadExclusive(context, address, exclusive, size); + + Operand valueLow = context.ConvertI64ToI32(value); + + valueLow = context.ZeroExtend32(OperandType.I64, valueLow); + + Operand valueHigh = context.ShiftRightUI(value, Const(32)); + + Operand lblBigEndian = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBigEndian, GetFlag(PState.EFlag)); + + SetIntA32(context, op.Rt, valueLow); + SetIntA32(context, op.Rt2, valueHigh); + + context.Branch(lblEnd); + + context.MarkLabel(lblBigEndian); + + SetIntA32(context, op.Rt2, valueLow); + SetIntA32(context, op.Rt, valueHigh); + + context.MarkLabel(lblEnd); + } + else + { + SetIntA32(context, op.Rt, EmitLoadExclusive(context, address, exclusive, size)); + } + } + else + { + if (size == DWordSizeLog2) + { + // Split the result into 2 words (based on endianness) + + Operand lo = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rt)); + Operand hi = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rt2)); + + Operand lblBigEndian = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBigEndian, GetFlag(PState.EFlag)); + + Operand leResult = context.BitwiseOr(lo, context.ShiftLeft(hi, Const(32))); + EmitStoreExclusive(context, address, leResult, exclusive, size, op.Rd, a32: true); + + context.Branch(lblEnd); + + context.MarkLabel(lblBigEndian); + + Operand beResult = context.BitwiseOr(hi, context.ShiftLeft(lo, Const(32))); + EmitStoreExclusive(context, address, beResult, exclusive, size, op.Rd, a32: true); + + context.MarkLabel(lblEnd); + } + else + { + Operand value = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rt)); + EmitStoreExclusive(context, address, value, exclusive, size, op.Rd, a32: true); + } + + if (ordered) + { + EmitBarrier(context); + } + } + } + + private static void EmitBarrier(ArmEmitterContext context) + { + // Note: This barrier is most likely not necessary, and probably + // doesn't make any difference since we need to do a ton of stuff + // (software MMU emulation) to read or write anything anyway. + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitMemoryExHelper.cs b/src/ARMeilleure/Instructions/InstEmitMemoryExHelper.cs new file mode 100644 index 00000000..9a69442a --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMemoryExHelper.cs @@ -0,0 +1,174 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitMemoryExHelper + { + private const int ErgSizeLog2 = 4; + + public static Operand EmitLoadExclusive(ArmEmitterContext context, Operand address, bool exclusive, int size) + { + if (exclusive) + { + Operand value; + + if (size == 4) + { + // Only 128-bit CAS is guaranteed to have a atomic load. + Operand physAddr = InstEmitMemoryHelper.EmitPtPointerLoad(context, address, default, write: false, 4); + + Operand zero = context.VectorZero(); + + value = context.CompareAndSwap(physAddr, zero, zero); + } + else + { + value = InstEmitMemoryHelper.EmitReadIntAligned(context, address, size); + } + + Operand arg0 = context.LoadArgument(OperandType.I64, 0); + + Operand exAddrPtr = context.Add(arg0, Const((long)NativeContext.GetExclusiveAddressOffset())); + Operand exValuePtr = context.Add(arg0, Const((long)NativeContext.GetExclusiveValueOffset())); + + context.Store(exAddrPtr, context.BitwiseAnd(address, Const(address.Type, GetExclusiveAddressMask()))); + + // Make sure the unused higher bits of the value are cleared. + if (size < 3) + { + context.Store(exValuePtr, Const(0UL)); + } + if (size < 4) + { + context.Store(context.Add(exValuePtr, Const(exValuePtr.Type, 8L)), Const(0UL)); + } + + // Store the new exclusive value. + context.Store(exValuePtr, value); + + return value; + } + else + { + return InstEmitMemoryHelper.EmitReadIntAligned(context, address, size); + } + } + + public static void EmitStoreExclusive( + ArmEmitterContext context, + Operand address, + Operand value, + bool exclusive, + int size, + int rs, + bool a32) + { + if (size < 3) + { + value = context.ConvertI64ToI32(value); + } + + if (exclusive) + { + // We overwrite one of the register (Rs), + // keep a copy of the values to ensure we are working with the correct values. + address = context.Copy(address); + value = context.Copy(value); + + void SetRs(Operand value) + { + if (a32) + { + SetIntA32(context, rs, value); + } + else + { + SetIntOrZR(context, rs, value); + } + } + + Operand arg0 = context.LoadArgument(OperandType.I64, 0); + + Operand exAddrPtr = context.Add(arg0, Const((long)NativeContext.GetExclusiveAddressOffset())); + Operand exAddr = context.Load(address.Type, exAddrPtr); + + // STEP 1: Check if we have exclusive access to this memory region. If not, fail and skip store. + Operand maskedAddress = context.BitwiseAnd(address, Const(address.Type, GetExclusiveAddressMask())); + + Operand exFailed = context.ICompareNotEqual(exAddr, maskedAddress); + + Operand lblExit = Label(); + + SetRs(Const(1)); + + context.BranchIfTrue(lblExit, exFailed); + + // STEP 2: We have exclusive access and the address is valid, attempt the store using CAS. + Operand physAddr = InstEmitMemoryHelper.EmitPtPointerLoad(context, address, default, write: true, size); + + Operand exValuePtr = context.Add(arg0, Const((long)NativeContext.GetExclusiveValueOffset())); + Operand exValue = size switch + { + 0 => context.Load8(exValuePtr), + 1 => context.Load16(exValuePtr), + 2 => context.Load(OperandType.I32, exValuePtr), + 3 => context.Load(OperandType.I64, exValuePtr), + _ => context.Load(OperandType.V128, exValuePtr) + }; + + Operand currValue = size switch + { + 0 => context.CompareAndSwap8(physAddr, exValue, value), + 1 => context.CompareAndSwap16(physAddr, exValue, value), + _ => context.CompareAndSwap(physAddr, exValue, value) + }; + + // STEP 3: Check if we succeeded by comparing expected and in-memory values. + Operand storeFailed; + + if (size == 4) + { + Operand currValueLow = context.VectorExtract(OperandType.I64, currValue, 0); + Operand currValueHigh = context.VectorExtract(OperandType.I64, currValue, 1); + + Operand exValueLow = context.VectorExtract(OperandType.I64, exValue, 0); + Operand exValueHigh = context.VectorExtract(OperandType.I64, exValue, 1); + + storeFailed = context.BitwiseOr( + context.ICompareNotEqual(currValueLow, exValueLow), + context.ICompareNotEqual(currValueHigh, exValueHigh)); + } + else + { + storeFailed = context.ICompareNotEqual(currValue, exValue); + } + + SetRs(storeFailed); + + context.MarkLabel(lblExit); + } + else + { + InstEmitMemoryHelper.EmitWriteIntAligned(context, address, value, size); + } + } + + public static void EmitClearExclusive(ArmEmitterContext context) + { + Operand arg0 = context.LoadArgument(OperandType.I64, 0); + + Operand exAddrPtr = context.Add(arg0, Const((long)NativeContext.GetExclusiveAddressOffset())); + + // We store ULONG max to force any exclusive address checks to fail, + // since this value is not aligned to the ERG mask. + context.Store(exAddrPtr, Const(ulong.MaxValue)); + } + + private static long GetExclusiveAddressMask() => ~((4L << ErgSizeLog2) - 1); + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs b/src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs new file mode 100644 index 00000000..f97e395c --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMemoryHelper.cs @@ -0,0 +1,648 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Memory; +using ARMeilleure.Translation; +using ARMeilleure.Translation.PTC; +using System; +using System.Reflection; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitMemoryHelper + { + private const int PageBits = 12; + private const int PageMask = (1 << PageBits) - 1; + + private enum Extension + { + Zx, + Sx32, + Sx64 + } + + public static void EmitLoadZx(ArmEmitterContext context, Operand address, int rt, int size) + { + EmitLoad(context, address, Extension.Zx, rt, size); + } + + public static void EmitLoadSx32(ArmEmitterContext context, Operand address, int rt, int size) + { + EmitLoad(context, address, Extension.Sx32, rt, size); + } + + public static void EmitLoadSx64(ArmEmitterContext context, Operand address, int rt, int size) + { + EmitLoad(context, address, Extension.Sx64, rt, size); + } + + private static void EmitLoad(ArmEmitterContext context, Operand address, Extension ext, int rt, int size) + { + bool isSimd = IsSimd(context); + + if ((uint)size > (isSimd ? 4 : 3)) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + if (isSimd) + { + EmitReadVector(context, address, context.VectorZero(), rt, 0, size); + } + else + { + EmitReadInt(context, address, rt, size); + } + + if (!isSimd && !(context.CurrOp is OpCode32 && rt == State.RegisterAlias.Aarch32Pc)) + { + Operand value = GetInt(context, rt); + + if (ext == Extension.Sx32 || ext == Extension.Sx64) + { + OperandType destType = ext == Extension.Sx64 ? OperandType.I64 : OperandType.I32; + + switch (size) + { + case 0: value = context.SignExtend8 (destType, value); break; + case 1: value = context.SignExtend16(destType, value); break; + case 2: value = context.SignExtend32(destType, value); break; + } + } + + SetInt(context, rt, value); + } + } + + public static void EmitLoadSimd( + ArmEmitterContext context, + Operand address, + Operand vector, + int rt, + int elem, + int size) + { + EmitReadVector(context, address, vector, rt, elem, size); + } + + public static void EmitStore(ArmEmitterContext context, Operand address, int rt, int size) + { + bool isSimd = IsSimd(context); + + if ((uint)size > (isSimd ? 4 : 3)) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + if (isSimd) + { + EmitWriteVector(context, address, rt, 0, size); + } + else + { + EmitWriteInt(context, address, rt, size); + } + } + + public static void EmitStoreSimd( + ArmEmitterContext context, + Operand address, + int rt, + int elem, + int size) + { + EmitWriteVector(context, address, rt, elem, size); + } + + private static bool IsSimd(ArmEmitterContext context) + { + return context.CurrOp is IOpCodeSimd && + !(context.CurrOp is OpCodeSimdMemMs || + context.CurrOp is OpCodeSimdMemSs); + } + + public static Operand EmitReadInt(ArmEmitterContext context, Operand address, int size) + { + Operand temp = context.AllocateLocal(size == 3 ? OperandType.I64 : OperandType.I32); + + Operand lblSlowPath = Label(); + Operand lblEnd = Label(); + + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: false, size); + + Operand value = default; + + switch (size) + { + case 0: value = context.Load8 (physAddr); break; + case 1: value = context.Load16(physAddr); break; + case 2: value = context.Load (OperandType.I32, physAddr); break; + case 3: value = context.Load (OperandType.I64, physAddr); break; + } + + context.Copy(temp, value); + + if (!context.Memory.Type.IsHostMapped()) + { + context.Branch(lblEnd); + + context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold); + + context.Copy(temp, EmitReadIntFallback(context, address, size)); + + context.MarkLabel(lblEnd); + } + + return temp; + } + + private static void EmitReadInt(ArmEmitterContext context, Operand address, int rt, int size) + { + Operand lblSlowPath = Label(); + Operand lblEnd = Label(); + + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: false, size); + + Operand value = default; + + switch (size) + { + case 0: value = context.Load8 (physAddr); break; + case 1: value = context.Load16(physAddr); break; + case 2: value = context.Load (OperandType.I32, physAddr); break; + case 3: value = context.Load (OperandType.I64, physAddr); break; + } + + SetInt(context, rt, value); + + if (!context.Memory.Type.IsHostMapped()) + { + context.Branch(lblEnd); + + context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold); + + EmitReadIntFallback(context, address, rt, size); + + context.MarkLabel(lblEnd); + } + } + + public static Operand EmitReadIntAligned(ArmEmitterContext context, Operand address, int size) + { + if ((uint)size > 4) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + Operand physAddr = EmitPtPointerLoad(context, address, default, write: false, size); + + return size switch + { + 0 => context.Load8(physAddr), + 1 => context.Load16(physAddr), + 2 => context.Load(OperandType.I32, physAddr), + 3 => context.Load(OperandType.I64, physAddr), + _ => context.Load(OperandType.V128, physAddr) + }; + } + + private static void EmitReadVector( + ArmEmitterContext context, + Operand address, + Operand vector, + int rt, + int elem, + int size) + { + Operand lblSlowPath = Label(); + Operand lblEnd = Label(); + + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: false, size); + + Operand value = default; + + switch (size) + { + case 0: value = context.VectorInsert8 (vector, context.Load8(physAddr), elem); break; + case 1: value = context.VectorInsert16(vector, context.Load16(physAddr), elem); break; + case 2: value = context.VectorInsert (vector, context.Load(OperandType.I32, physAddr), elem); break; + case 3: value = context.VectorInsert (vector, context.Load(OperandType.I64, physAddr), elem); break; + case 4: value = context.Load (OperandType.V128, physAddr); break; + } + + context.Copy(GetVec(rt), value); + + if (!context.Memory.Type.IsHostMapped()) + { + context.Branch(lblEnd); + + context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold); + + EmitReadVectorFallback(context, address, vector, rt, elem, size); + + context.MarkLabel(lblEnd); + } + } + + private static Operand VectorCreate(ArmEmitterContext context, Operand value) + { + return context.VectorInsert(context.VectorZero(), value, 0); + } + + private static void EmitWriteInt(ArmEmitterContext context, Operand address, int rt, int size) + { + Operand lblSlowPath = Label(); + Operand lblEnd = Label(); + + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: true, size); + + Operand value = GetInt(context, rt); + + if (size < 3 && value.Type == OperandType.I64) + { + value = context.ConvertI64ToI32(value); + } + + switch (size) + { + case 0: context.Store8 (physAddr, value); break; + case 1: context.Store16(physAddr, value); break; + case 2: context.Store (physAddr, value); break; + case 3: context.Store (physAddr, value); break; + } + + if (!context.Memory.Type.IsHostMapped()) + { + context.Branch(lblEnd); + + context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold); + + EmitWriteIntFallback(context, address, rt, size); + + context.MarkLabel(lblEnd); + } + } + + public static void EmitWriteIntAligned(ArmEmitterContext context, Operand address, Operand value, int size) + { + if ((uint)size > 4) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + Operand physAddr = EmitPtPointerLoad(context, address, default, write: true, size); + + if (size < 3 && value.Type == OperandType.I64) + { + value = context.ConvertI64ToI32(value); + } + + if (size == 0) + { + context.Store8(physAddr, value); + } + else if (size == 1) + { + context.Store16(physAddr, value); + } + else + { + context.Store(physAddr, value); + } + } + + private static void EmitWriteVector( + ArmEmitterContext context, + Operand address, + int rt, + int elem, + int size) + { + Operand lblSlowPath = Label(); + Operand lblEnd = Label(); + + Operand physAddr = EmitPtPointerLoad(context, address, lblSlowPath, write: true, size); + + Operand value = GetVec(rt); + + switch (size) + { + case 0: context.Store8 (physAddr, context.VectorExtract8(value, elem)); break; + case 1: context.Store16(physAddr, context.VectorExtract16(value, elem)); break; + case 2: context.Store (physAddr, context.VectorExtract(OperandType.I32, value, elem)); break; + case 3: context.Store (physAddr, context.VectorExtract(OperandType.I64, value, elem)); break; + case 4: context.Store (physAddr, value); break; + } + + if (!context.Memory.Type.IsHostMapped()) + { + context.Branch(lblEnd); + + context.MarkLabel(lblSlowPath, BasicBlockFrequency.Cold); + + EmitWriteVectorFallback(context, address, rt, elem, size); + + context.MarkLabel(lblEnd); + } + } + + public static Operand EmitPtPointerLoad(ArmEmitterContext context, Operand address, Operand lblSlowPath, bool write, int size) + { + if (context.Memory.Type.IsHostMapped()) + { + return EmitHostMappedPointer(context, address); + } + + int ptLevelBits = context.Memory.AddressSpaceBits - PageBits; + int ptLevelSize = 1 << ptLevelBits; + int ptLevelMask = ptLevelSize - 1; + + Operand addrRotated = size != 0 ? context.RotateRight(address, Const(size)) : address; + Operand addrShifted = context.ShiftRightUI(addrRotated, Const(PageBits - size)); + + Operand pte = !context.HasPtc + ? Const(context.Memory.PageTablePointer.ToInt64()) + : Const(context.Memory.PageTablePointer.ToInt64(), Ptc.PageTableSymbol); + + Operand pteOffset = context.BitwiseAnd(addrShifted, Const(addrShifted.Type, ptLevelMask)); + + if (pteOffset.Type == OperandType.I32) + { + pteOffset = context.ZeroExtend32(OperandType.I64, pteOffset); + } + + pte = context.Load(OperandType.I64, context.Add(pte, context.ShiftLeft(pteOffset, Const(3)))); + + if (addrShifted.Type == OperandType.I32) + { + addrShifted = context.ZeroExtend32(OperandType.I64, addrShifted); + } + + // If the VA is out of range, or not aligned to the access size, force PTE to 0 by masking it. + pte = context.BitwiseAnd(pte, context.ShiftRightSI(context.Add(addrShifted, Const(-(long)ptLevelSize)), Const(63))); + + if (lblSlowPath != default) + { + if (write) + { + context.BranchIf(lblSlowPath, pte, Const(0L), Comparison.LessOrEqual); + pte = context.BitwiseAnd(pte, Const(0xffffffffffffUL)); // Ignore any software protection bits. (they are still used by C# memory access) + } + else + { + pte = context.ShiftLeft(pte, Const(1)); + context.BranchIf(lblSlowPath, pte, Const(0L), Comparison.LessOrEqual); + pte = context.ShiftRightUI(pte, Const(1)); + } + } + else + { + // When no label is provided to jump to a slow path if the address is invalid, + // we do the validation ourselves, and throw if needed. + + Operand lblNotWatched = Label(); + + // Is the page currently being tracked for read/write? If so we need to call SignalMemoryTracking. + context.BranchIf(lblNotWatched, pte, Const(0L), Comparison.GreaterOrEqual, BasicBlockFrequency.Cold); + + // Signal memory tracking. Size here doesn't matter as address is assumed to be size aligned here. + context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SignalMemoryTracking)), address, Const(1UL), Const(write ? 1 : 0)); + context.MarkLabel(lblNotWatched); + + pte = context.BitwiseAnd(pte, Const(0xffffffffffffUL)); // Ignore any software protection bits. (they are still used by C# memory access) + + Operand lblNonNull = Label(); + + // Skip exception if the PTE address is non-null (not zero). + context.BranchIfTrue(lblNonNull, pte, BasicBlockFrequency.Cold); + + // The call is not expected to return (it should throw). + context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ThrowInvalidMemoryAccess)), address); + context.MarkLabel(lblNonNull); + } + + Operand pageOffset = context.BitwiseAnd(address, Const(address.Type, PageMask)); + + if (pageOffset.Type == OperandType.I32) + { + pageOffset = context.ZeroExtend32(OperandType.I64, pageOffset); + } + + return context.Add(pte, pageOffset); + } + + public static Operand EmitHostMappedPointer(ArmEmitterContext context, Operand address) + { + if (address.Type == OperandType.I32) + { + address = context.ZeroExtend32(OperandType.I64, address); + } + + if (context.Memory.Type == MemoryManagerType.HostMapped) + { + Operand mask = Const(ulong.MaxValue >> (64 - context.Memory.AddressSpaceBits)); + address = context.BitwiseAnd(address, mask); + } + + Operand baseAddr = !context.HasPtc + ? Const(context.Memory.PageTablePointer.ToInt64()) + : Const(context.Memory.PageTablePointer.ToInt64(), Ptc.PageTableSymbol); + + return context.Add(baseAddr, address); + } + + private static void EmitReadIntFallback(ArmEmitterContext context, Operand address, int rt, int size) + { + SetInt(context, rt, EmitReadIntFallback(context, address, size)); + } + + private static Operand EmitReadIntFallback(ArmEmitterContext context, Operand address, int size) + { + MethodInfo info = null; + + switch (size) + { + case 0: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadByte)); break; + case 1: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt16)); break; + case 2: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt32)); break; + case 3: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64)); break; + } + + return context.Call(info, address); + } + + private static void EmitReadVectorFallback( + ArmEmitterContext context, + Operand address, + Operand vector, + int rt, + int elem, + int size) + { + MethodInfo info = null; + + switch (size) + { + case 0: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadByte)); break; + case 1: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt16)); break; + case 2: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt32)); break; + case 3: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64)); break; + case 4: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadVector128)); break; + } + + Operand value = context.Call(info, address); + + switch (size) + { + case 0: value = context.VectorInsert8 (vector, value, elem); break; + case 1: value = context.VectorInsert16(vector, value, elem); break; + case 2: value = context.VectorInsert (vector, value, elem); break; + case 3: value = context.VectorInsert (vector, value, elem); break; + } + + context.Copy(GetVec(rt), value); + } + + private static void EmitWriteIntFallback(ArmEmitterContext context, Operand address, int rt, int size) + { + MethodInfo info = null; + + switch (size) + { + case 0: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteByte)); break; + case 1: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt16)); break; + case 2: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt32)); break; + case 3: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt64)); break; + } + + Operand value = GetInt(context, rt); + + if (size < 3 && value.Type == OperandType.I64) + { + value = context.ConvertI64ToI32(value); + } + + context.Call(info, address, value); + } + + private static void EmitWriteVectorFallback( + ArmEmitterContext context, + Operand address, + int rt, + int elem, + int size) + { + MethodInfo info = null; + + switch (size) + { + case 0: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteByte)); break; + case 1: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt16)); break; + case 2: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt32)); break; + case 3: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt64)); break; + case 4: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteVector128)); break; + } + + Operand value = default; + + if (size < 4) + { + switch (size) + { + case 0: value = context.VectorExtract8 (GetVec(rt), elem); break; + case 1: value = context.VectorExtract16(GetVec(rt), elem); break; + case 2: value = context.VectorExtract (OperandType.I32, GetVec(rt), elem); break; + case 3: value = context.VectorExtract (OperandType.I64, GetVec(rt), elem); break; + } + } + else + { + value = GetVec(rt); + } + + context.Call(info, address, value); + } + + private static Operand GetInt(ArmEmitterContext context, int rt) + { + return context.CurrOp is OpCode32 ? GetIntA32(context, rt) : GetIntOrZR(context, rt); + } + + private static void SetInt(ArmEmitterContext context, int rt, Operand value) + { + if (context.CurrOp is OpCode32) + { + SetIntA32(context, rt, value); + } + else + { + SetIntOrZR(context, rt, value); + } + } + + // ARM32 helpers. + public static Operand GetMemM(ArmEmitterContext context, bool setCarry = true) + { + switch (context.CurrOp) + { + case IOpCode32MemRsImm op: return GetMShiftedByImmediate(context, op, setCarry); + + case IOpCode32MemReg op: return GetIntA32(context, op.Rm); + + case IOpCode32Mem op: return Const(op.Immediate); + + case OpCode32SimdMemImm op: return Const(op.Immediate); + + default: throw InvalidOpCodeType(context.CurrOp); + } + } + + private static Exception InvalidOpCodeType(OpCode opCode) + { + return new InvalidOperationException($"Invalid OpCode type \"{opCode?.GetType().Name ?? "null"}\"."); + } + + public static Operand GetMShiftedByImmediate(ArmEmitterContext context, IOpCode32MemRsImm op, bool setCarry) + { + Operand m = GetIntA32(context, op.Rm); + + int shift = op.Immediate; + + if (shift == 0) + { + switch (op.ShiftType) + { + case ShiftType.Lsr: shift = 32; break; + case ShiftType.Asr: shift = 32; break; + case ShiftType.Ror: shift = 1; break; + } + } + + if (shift != 0) + { + setCarry &= false; + + switch (op.ShiftType) + { + case ShiftType.Lsl: m = InstEmitAluHelper.GetLslC(context, m, setCarry, shift); break; + case ShiftType.Lsr: m = InstEmitAluHelper.GetLsrC(context, m, setCarry, shift); break; + case ShiftType.Asr: m = InstEmitAluHelper.GetAsrC(context, m, setCarry, shift); break; + case ShiftType.Ror: + if (op.Immediate != 0) + { + m = InstEmitAluHelper.GetRorC(context, m, setCarry, shift); + } + else + { + m = InstEmitAluHelper.GetRrxC(context, m, setCarry); + } + break; + } + } + + return m; + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitMove.cs b/src/ARMeilleure/Instructions/InstEmitMove.cs new file mode 100644 index 00000000..d551bf2d --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMove.cs @@ -0,0 +1,41 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Movk(ArmEmitterContext context) + { + OpCodeMov op = (OpCodeMov)context.CurrOp; + + OperandType type = op.GetOperandType(); + + Operand res = GetIntOrZR(context, op.Rd); + + res = context.BitwiseAnd(res, Const(type, ~(0xffffL << op.Bit))); + + res = context.BitwiseOr(res, Const(type, op.Immediate)); + + SetIntOrZR(context, op.Rd, res); + } + + public static void Movn(ArmEmitterContext context) + { + OpCodeMov op = (OpCodeMov)context.CurrOp; + + SetIntOrZR(context, op.Rd, Const(op.GetOperandType(), ~op.Immediate)); + } + + public static void Movz(ArmEmitterContext context) + { + OpCodeMov op = (OpCodeMov)context.CurrOp; + + SetIntOrZR(context, op.Rd, Const(op.GetOperandType(), op.Immediate)); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Instructions/InstEmitMul.cs b/src/ARMeilleure/Instructions/InstEmitMul.cs new file mode 100644 index 00000000..65d11b30 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMul.cs @@ -0,0 +1,100 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; + +using static ARMeilleure.Instructions.InstEmitHelper; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Madd(ArmEmitterContext context) => EmitMul(context, isAdd: true); + public static void Msub(ArmEmitterContext context) => EmitMul(context, isAdd: false); + + private static void EmitMul(ArmEmitterContext context, bool isAdd) + { + OpCodeMul op = (OpCodeMul)context.CurrOp; + + Operand a = GetIntOrZR(context, op.Ra); + Operand n = GetIntOrZR(context, op.Rn); + Operand m = GetIntOrZR(context, op.Rm); + + Operand res = context.Multiply(n, m); + + res = isAdd ? context.Add(a, res) : context.Subtract(a, res); + + SetIntOrZR(context, op.Rd, res); + } + + public static void Smaddl(ArmEmitterContext context) => EmitMull(context, MullFlags.SignedAdd); + public static void Smsubl(ArmEmitterContext context) => EmitMull(context, MullFlags.SignedSubtract); + public static void Umaddl(ArmEmitterContext context) => EmitMull(context, MullFlags.Add); + public static void Umsubl(ArmEmitterContext context) => EmitMull(context, MullFlags.Subtract); + + [Flags] + private enum MullFlags + { + Subtract = 0, + Add = 1 << 0, + Signed = 1 << 1, + + SignedAdd = Signed | Add, + SignedSubtract = Signed | Subtract + } + + private static void EmitMull(ArmEmitterContext context, MullFlags flags) + { + OpCodeMul op = (OpCodeMul)context.CurrOp; + + Operand GetExtendedRegister32(int index) + { + Operand value = GetIntOrZR(context, index); + + if ((flags & MullFlags.Signed) != 0) + { + return context.SignExtend32(value.Type, value); + } + else + { + return context.ZeroExtend32(value.Type, value); + } + } + + Operand a = GetIntOrZR(context, op.Ra); + + Operand n = GetExtendedRegister32(op.Rn); + Operand m = GetExtendedRegister32(op.Rm); + + Operand res = context.Multiply(n, m); + + res = (flags & MullFlags.Add) != 0 ? context.Add(a, res) : context.Subtract(a, res); + + SetIntOrZR(context, op.Rd, res); + } + + public static void Smulh(ArmEmitterContext context) + { + OpCodeMul op = (OpCodeMul)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + Operand m = GetIntOrZR(context, op.Rm); + + Operand d = context.Multiply64HighSI(n, m); + + SetIntOrZR(context, op.Rd, d); + } + + public static void Umulh(ArmEmitterContext context) + { + OpCodeMul op = (OpCodeMul)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + Operand m = GetIntOrZR(context, op.Rm); + + Operand d = context.Multiply64HighUI(n, m); + + SetIntOrZR(context, op.Rd, d); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Instructions/InstEmitMul32.cs b/src/ARMeilleure/Instructions/InstEmitMul32.cs new file mode 100644 index 00000000..0822f92c --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitMul32.cs @@ -0,0 +1,379 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; + +using static ARMeilleure.Instructions.InstEmitAluHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + [Flags] + private enum MullFlags + { + Subtract = 1, + Add = 1 << 1, + Signed = 1 << 2, + + SignedAdd = Signed | Add, + SignedSubtract = Signed | Subtract + } + + public static void Mla(ArmEmitterContext context) + { + IOpCode32AluMla op = (IOpCode32AluMla)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + Operand a = GetIntA32(context, op.Ra); + + Operand res = context.Add(a, context.Multiply(n, m)); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitAluStore(context, res); + } + + public static void Mls(ArmEmitterContext context) + { + IOpCode32AluMla op = (IOpCode32AluMla)context.CurrOp; + + Operand n = GetAluN(context); + Operand m = GetAluM(context); + Operand a = GetIntA32(context, op.Ra); + + Operand res = context.Subtract(a, context.Multiply(n, m)); + + EmitAluStore(context, res); + } + + public static void Smmla(ArmEmitterContext context) + { + EmitSmmul(context, MullFlags.SignedAdd); + } + + public static void Smmls(ArmEmitterContext context) + { + EmitSmmul(context, MullFlags.SignedSubtract); + } + + public static void Smmul(ArmEmitterContext context) + { + EmitSmmul(context, MullFlags.Signed); + } + + private static void EmitSmmul(ArmEmitterContext context, MullFlags flags) + { + IOpCode32AluMla op = (IOpCode32AluMla)context.CurrOp; + + Operand n = context.SignExtend32(OperandType.I64, GetIntA32(context, op.Rn)); + Operand m = context.SignExtend32(OperandType.I64, GetIntA32(context, op.Rm)); + + Operand res = context.Multiply(n, m); + + if (flags.HasFlag(MullFlags.Add) && op.Ra != 0xf) + { + res = context.Add(context.ShiftLeft(context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Ra)), Const(32)), res); + } + else if (flags.HasFlag(MullFlags.Subtract)) + { + res = context.Subtract(context.ShiftLeft(context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Ra)), Const(32)), res); + } + + if (op.R) + { + res = context.Add(res, Const(0x80000000L)); + } + + Operand hi = context.ConvertI64ToI32(context.ShiftRightSI(res, Const(32))); + + EmitGenericAluStoreA32(context, op.Rd, false, hi); + } + + public static void Smla__(ArmEmitterContext context) + { + IOpCode32AluMla op = (IOpCode32AluMla)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + Operand a = GetIntA32(context, op.Ra); + + if (op.NHigh) + { + n = context.SignExtend16(OperandType.I64, context.ShiftRightUI(n, Const(16))); + } + else + { + n = context.SignExtend16(OperandType.I64, n); + } + + if (op.MHigh) + { + m = context.SignExtend16(OperandType.I64, context.ShiftRightUI(m, Const(16))); + } + else + { + m = context.SignExtend16(OperandType.I64, m); + } + + Operand res = context.Multiply(n, m); + + Operand toAdd = context.SignExtend32(OperandType.I64, a); + res = context.Add(res, toAdd); + Operand q = context.ICompareNotEqual(res, context.SignExtend32(OperandType.I64, res)); + res = context.ConvertI64ToI32(res); + + UpdateQFlag(context, q); + + EmitGenericAluStoreA32(context, op.Rd, false, res); + } + + public static void Smlal(ArmEmitterContext context) + { + EmitMlal(context, true); + } + + public static void Smlal__(ArmEmitterContext context) + { + IOpCode32AluUmull op = (IOpCode32AluUmull)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + + if (op.NHigh) + { + n = context.SignExtend16(OperandType.I64, context.ShiftRightUI(n, Const(16))); + } + else + { + n = context.SignExtend16(OperandType.I64, n); + } + + if (op.MHigh) + { + m = context.SignExtend16(OperandType.I64, context.ShiftRightUI(m, Const(16))); + } + else + { + m = context.SignExtend16(OperandType.I64, m); + } + + Operand res = context.Multiply(n, m); + + Operand toAdd = context.ShiftLeft(context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.RdHi)), Const(32)); + toAdd = context.BitwiseOr(toAdd, context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.RdLo))); + res = context.Add(res, toAdd); + + Operand hi = context.ConvertI64ToI32(context.ShiftRightUI(res, Const(32))); + Operand lo = context.ConvertI64ToI32(res); + + EmitGenericAluStoreA32(context, op.RdHi, false, hi); + EmitGenericAluStoreA32(context, op.RdLo, false, lo); + } + + public static void Smlaw_(ArmEmitterContext context) + { + IOpCode32AluMla op = (IOpCode32AluMla)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + Operand a = GetIntA32(context, op.Ra); + + if (op.MHigh) + { + m = context.SignExtend16(OperandType.I64, context.ShiftRightUI(m, Const(16))); + } + else + { + m = context.SignExtend16(OperandType.I64, m); + } + + Operand res = context.Multiply(context.SignExtend32(OperandType.I64, n), m); + + Operand toAdd = context.ShiftLeft(context.SignExtend32(OperandType.I64, a), Const(16)); + res = context.Add(res, toAdd); + res = context.ShiftRightSI(res, Const(16)); + Operand q = context.ICompareNotEqual(res, context.SignExtend32(OperandType.I64, res)); + res = context.ConvertI64ToI32(res); + + UpdateQFlag(context, q); + + EmitGenericAluStoreA32(context, op.Rd, false, res); + } + + public static void Smul__(ArmEmitterContext context) + { + IOpCode32AluMla op = (IOpCode32AluMla)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + + if (op.NHigh) + { + n = context.ShiftRightSI(n, Const(16)); + } + else + { + n = context.SignExtend16(OperandType.I32, n); + } + + if (op.MHigh) + { + m = context.ShiftRightSI(m, Const(16)); + } + else + { + m = context.SignExtend16(OperandType.I32, m); + } + + Operand res = context.Multiply(n, m); + + EmitGenericAluStoreA32(context, op.Rd, false, res); + } + + public static void Smull(ArmEmitterContext context) + { + IOpCode32AluUmull op = (IOpCode32AluUmull)context.CurrOp; + + Operand n = context.SignExtend32(OperandType.I64, GetIntA32(context, op.Rn)); + Operand m = context.SignExtend32(OperandType.I64, GetIntA32(context, op.Rm)); + + Operand res = context.Multiply(n, m); + + Operand hi = context.ConvertI64ToI32(context.ShiftRightUI(res, Const(32))); + Operand lo = context.ConvertI64ToI32(res); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitGenericAluStoreA32(context, op.RdHi, ShouldSetFlags(context), hi); + EmitGenericAluStoreA32(context, op.RdLo, ShouldSetFlags(context), lo); + } + + public static void Smulw_(ArmEmitterContext context) + { + IOpCode32AluMla op = (IOpCode32AluMla)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + + if (op.MHigh) + { + m = context.SignExtend16(OperandType.I64, context.ShiftRightUI(m, Const(16))); + } + else + { + m = context.SignExtend16(OperandType.I64, m); + } + + Operand res = context.Multiply(context.SignExtend32(OperandType.I64, n), m); + + res = context.ShiftRightUI(res, Const(16)); + res = context.ConvertI64ToI32(res); + + EmitGenericAluStoreA32(context, op.Rd, false, res); + } + + public static void Umaal(ArmEmitterContext context) + { + IOpCode32AluUmull op = (IOpCode32AluUmull)context.CurrOp; + + Operand n = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rn)); + Operand m = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rm)); + Operand dHi = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.RdHi)); + Operand dLo = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.RdLo)); + + Operand res = context.Multiply(n, m); + res = context.Add(res, dHi); + res = context.Add(res, dLo); + + Operand hi = context.ConvertI64ToI32(context.ShiftRightUI(res, Const(32))); + Operand lo = context.ConvertI64ToI32(res); + + EmitGenericAluStoreA32(context, op.RdHi, false, hi); + EmitGenericAluStoreA32(context, op.RdLo, false, lo); + } + + public static void Umlal(ArmEmitterContext context) + { + EmitMlal(context, false); + } + + public static void Umull(ArmEmitterContext context) + { + IOpCode32AluUmull op = (IOpCode32AluUmull)context.CurrOp; + + Operand n = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rn)); + Operand m = context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.Rm)); + + Operand res = context.Multiply(n, m); + + Operand hi = context.ConvertI64ToI32(context.ShiftRightUI(res, Const(32))); + Operand lo = context.ConvertI64ToI32(res); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitGenericAluStoreA32(context, op.RdHi, ShouldSetFlags(context), hi); + EmitGenericAluStoreA32(context, op.RdLo, ShouldSetFlags(context), lo); + } + + private static void EmitMlal(ArmEmitterContext context, bool signed) + { + IOpCode32AluUmull op = (IOpCode32AluUmull)context.CurrOp; + + Operand n = GetIntA32(context, op.Rn); + Operand m = GetIntA32(context, op.Rm); + + if (signed) + { + n = context.SignExtend32(OperandType.I64, n); + m = context.SignExtend32(OperandType.I64, m); + } + else + { + n = context.ZeroExtend32(OperandType.I64, n); + m = context.ZeroExtend32(OperandType.I64, m); + } + + Operand res = context.Multiply(n, m); + + Operand toAdd = context.ShiftLeft(context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.RdHi)), Const(32)); + toAdd = context.BitwiseOr(toAdd, context.ZeroExtend32(OperandType.I64, GetIntA32(context, op.RdLo))); + res = context.Add(res, toAdd); + + Operand hi = context.ConvertI64ToI32(context.ShiftRightUI(res, Const(32))); + Operand lo = context.ConvertI64ToI32(res); + + if (ShouldSetFlags(context)) + { + EmitNZFlagsCheck(context, res); + } + + EmitGenericAluStoreA32(context, op.RdHi, ShouldSetFlags(context), hi); + EmitGenericAluStoreA32(context, op.RdLo, ShouldSetFlags(context), lo); + } + + private static void UpdateQFlag(ArmEmitterContext context, Operand q) + { + Operand lblSkipSetQ = Label(); + + context.BranchIfFalse(lblSkipSetQ, q); + + SetFlag(context, PState.QFlag, Const(1)); + + context.MarkLabel(lblSkipSetQ); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs b/src/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs new file mode 100644 index 00000000..7e7f26b1 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdArithmetic.cs @@ -0,0 +1,5224 @@ +// https://github.com/intel/ARM_NEON_2_x86_SSE/blob/master/NEON_2_SSE.h +// https://www.agner.org/optimize/#vectorclass @ vectori128.h + +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper32; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + using Func2I = Func; + + static partial class InstEmit + { + public static void Abs_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOp(context, Intrinsic.Arm64AbsS); + } + else + { + EmitScalarUnaryOpSx(context, (op1) => EmitAbs(context, op1)); + } + } + + public static void Abs_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64AbsV); + } + else + { + EmitVectorUnaryOpSx(context, (op1) => EmitAbs(context, op1)); + } + } + + public static void Add_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOp(context, Intrinsic.Arm64AddS); + } + else + { + EmitScalarBinaryOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Add_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64AddV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + Operand res = context.AddIntrinsic(addInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Addhn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64AddhnV); + } + else + { + EmitHighNarrow(context, (op1, op2) => context.Add(op1, op2), round: false); + } + } + + public static void Addp_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOp(context, Intrinsic.Arm64AddpS); + } + else + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand ne0 = EmitVectorExtractZx(context, op.Rn, 0, op.Size); + Operand ne1 = EmitVectorExtractZx(context, op.Rn, 1, op.Size); + + Operand res = context.Add(ne0, ne1); + + context.Copy(GetVec(op.Rd), EmitVectorInsert(context, context.VectorZero(), res, 0, op.Size)); + } + } + + public static void Addp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64AddpV); + } + else if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PaddInstruction); + } + else + { + EmitVectorPairwiseOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Addv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64AddvV); + } + else + { + EmitVectorAcrossVectorOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Cls_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64ClsV); + } + else + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + int eSize = 8 << op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + + Operand de = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.CountLeadingSigns)), ne, Const(eSize)); + + res = EmitVectorInsert(context, res, de, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Clz_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64ClzV); + } + else + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int eSize = 8 << op.Size; + + Operand res = eSize switch { + 8 => Clz_V_I8 (context, GetVec(op.Rn)), + 16 => Clz_V_I16(context, GetVec(op.Rn)), + 32 => Clz_V_I32(context, GetVec(op.Rn)), + _ => default + }; + + if (res != default) + { + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + } + else + { + int elems = op.GetBytesCount() >> op.Size; + + res = context.VectorZero(); + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + + Operand de = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.CountLeadingZeros)), ne, Const(eSize)); + + res = EmitVectorInsert(context, res, de, index, op.Size); + } + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static Operand Clz_V_I8(ArmEmitterContext context, Operand arg) + { + if (!Optimizations.UseSsse3) + { + return default; + } + + // CLZ nibble table. + Operand clzTable = X86GetScalar(context, 0x01_01_01_01_02_02_03_04); + + Operand maskLow = X86GetAllElements(context, 0x0f_0f_0f_0f); + Operand c04 = X86GetAllElements(context, 0x04_04_04_04); + + // CLZ of low 4 bits of elements in arg. + Operand loClz = context.AddIntrinsic(Intrinsic.X86Pshufb, clzTable, arg); + + // Get the high 4 bits of elements in arg. + Operand hiArg = context.AddIntrinsic(Intrinsic.X86Psrlw, arg, Const(4)); + hiArg = context.AddIntrinsic(Intrinsic.X86Pand, hiArg, maskLow); + + // CLZ of high 4 bits of elements in arg. + Operand hiClz = context.AddIntrinsic(Intrinsic.X86Pshufb, clzTable, hiArg); + + // If high 4 bits are not all zero, we discard the CLZ of the low 4 bits. + Operand mask = context.AddIntrinsic(Intrinsic.X86Pcmpeqb, hiClz, c04); + loClz = context.AddIntrinsic(Intrinsic.X86Pand, loClz, mask); + + return context.AddIntrinsic(Intrinsic.X86Paddb, loClz, hiClz); + } + + private static Operand Clz_V_I16(ArmEmitterContext context, Operand arg) + { + if (!Optimizations.UseSsse3) + { + return default; + } + + Operand maskSwap = X86GetElements(context, 0x80_0f_80_0d_80_0b_80_09, 0x80_07_80_05_80_03_80_01); + Operand maskLow = X86GetAllElements(context, 0x00ff_00ff); + Operand c0008 = X86GetAllElements(context, 0x0008_0008); + + // CLZ pair of high 8 and low 8 bits of elements in arg. + Operand hiloClz = Clz_V_I8(context, arg); + // Get CLZ of low 8 bits in each pair. + Operand loClz = context.AddIntrinsic(Intrinsic.X86Pand, hiloClz, maskLow); + // Get CLZ of high 8 bits in each pair. + Operand hiClz = context.AddIntrinsic(Intrinsic.X86Pshufb, hiloClz, maskSwap); + + // If high 8 bits are not all zero, we discard the CLZ of the low 8 bits. + Operand mask = context.AddIntrinsic(Intrinsic.X86Pcmpeqw, hiClz, c0008); + loClz = context.AddIntrinsic(Intrinsic.X86Pand, loClz, mask); + + return context.AddIntrinsic(Intrinsic.X86Paddw, loClz, hiClz); + } + + private static Operand Clz_V_I32(ArmEmitterContext context, Operand arg) + { + // TODO: Use vplzcntd when AVX-512 is supported. + if (!Optimizations.UseSse2) + { + return default; + } + + Operand AddVectorI32(Operand op0, Operand op1) => context.AddIntrinsic(Intrinsic.X86Paddd, op0, op1); + Operand SubVectorI32(Operand op0, Operand op1) => context.AddIntrinsic(Intrinsic.X86Psubd, op0, op1); + Operand ShiftRightVectorUI32(Operand op0, int imm8) => context.AddIntrinsic(Intrinsic.X86Psrld, op0, Const(imm8)); + Operand OrVector(Operand op0, Operand op1) => context.AddIntrinsic(Intrinsic.X86Por, op0, op1); + Operand AndVector(Operand op0, Operand op1) => context.AddIntrinsic(Intrinsic.X86Pand, op0, op1); + Operand NotVector(Operand op0) => context.AddIntrinsic(Intrinsic.X86Pandn, op0, context.VectorOne()); + + Operand c55555555 = X86GetAllElements(context, 0x55555555); + Operand c33333333 = X86GetAllElements(context, 0x33333333); + Operand c0f0f0f0f = X86GetAllElements(context, 0x0f0f0f0f); + Operand c0000003f = X86GetAllElements(context, 0x0000003f); + + Operand tmp0; + Operand tmp1; + Operand res; + + // Set all bits after highest set bit to 1. + res = OrVector(ShiftRightVectorUI32(arg, 1), arg); + res = OrVector(ShiftRightVectorUI32(res, 2), res); + res = OrVector(ShiftRightVectorUI32(res, 4), res); + res = OrVector(ShiftRightVectorUI32(res, 8), res); + res = OrVector(ShiftRightVectorUI32(res, 16), res); + + // Make leading 0s into leading 1s. + res = NotVector(res); + + // Count leading 1s, which is the population count. + tmp0 = ShiftRightVectorUI32(res, 1); + tmp0 = AndVector(tmp0, c55555555); + res = SubVectorI32(res, tmp0); + + tmp0 = ShiftRightVectorUI32(res, 2); + tmp0 = AndVector(tmp0, c33333333); + tmp1 = AndVector(res, c33333333); + res = AddVectorI32(tmp0, tmp1); + + tmp0 = ShiftRightVectorUI32(res, 4); + tmp0 = AddVectorI32(tmp0, res); + res = AndVector(tmp0, c0f0f0f0f); + + tmp0 = ShiftRightVectorUI32(res, 8); + res = AddVectorI32(tmp0, res); + + tmp0 = ShiftRightVectorUI32(res, 16); + res = AddVectorI32(tmp0, res); + + res = AndVector(res, c0000003f); + + return res; + } + + public static void Cnt_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64CntV); + } + else + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.RegisterSize == RegisterSize.Simd128 ? 16 : 8; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, 0); + + Operand de; + + if (Optimizations.UsePopCnt) + { + de = context.AddIntrinsicLong(Intrinsic.X86Popcnt, ne); + } + else + { + de = EmitCountSetBits8(context, ne); + } + + res = EmitVectorInsert(context, res, de, index, 0); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Fabd_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FabdS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand res = context.AddIntrinsic(Intrinsic.X86Subss, GetVec(op.Rn), GetVec(op.Rm)); + + res = EmitFloatAbs(context, res, true, false); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else /* if (sizeF == 1) */ + { + Operand res = context.AddIntrinsic(Intrinsic.X86Subsd, GetVec(op.Rn), GetVec(op.Rm)); + + res = EmitFloatAbs(context, res, false, false); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), op1, op2); + + return EmitUnaryMathCall(context, nameof(Math.Abs), res); + }); + } + } + + public static void Fabd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FabdV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand res = context.AddIntrinsic(Intrinsic.X86Subps, GetVec(op.Rn), GetVec(op.Rm)); + + res = EmitFloatAbs(context, res, true, true); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + Operand res = context.AddIntrinsic(Intrinsic.X86Subpd, GetVec(op.Rn), GetVec(op.Rm)); + + res = EmitFloatAbs(context, res, false, true); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), op1, op2); + + return EmitUnaryMathCall(context, nameof(Math.Abs), res); + }); + } + } + + public static void Fabs_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FabsS); + } + else if (Optimizations.UseSse2) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + if (op.Size == 0) + { + Operand res = EmitFloatAbs(context, GetVec(op.Rn), true, false); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else /* if (op.Size == 1) */ + { + Operand res = EmitFloatAbs(context, GetVec(op.Rn), false, false); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Abs), op1); + }); + } + } + + public static void Fabs_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FabsV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand res = EmitFloatAbs(context, GetVec(op.Rn), true, true); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + Operand res = EmitFloatAbs(context, GetVec(op.Rn), false, true); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Abs), op1); + }); + } + } + + public static void Fadd_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FaddS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF(context, Intrinsic.X86Addss, Intrinsic.X86Addsd); + } + else if (Optimizations.FastFP) + { + EmitScalarBinaryOpF(context, (op1, op2) => context.Add(op1, op2)); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPAdd), op1, op2); + }); + } + } + + public static void Fadd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FaddV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF(context, Intrinsic.X86Addps, Intrinsic.X86Addpd); + } + else if (Optimizations.FastFP) + { + EmitVectorBinaryOpF(context, (op1, op2) => context.Add(op1, op2)); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPAdd), op1, op2); + }); + } + } + + public static void Faddp_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FaddpS); + } + else if (Optimizations.FastFP && Optimizations.UseSse3) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + if ((op.Size & 1) == 0) + { + Operand res = context.AddIntrinsic(Intrinsic.X86Haddps, GetVec(op.Rn), GetVec(op.Rn)); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else /* if ((op.Size & 1) == 1) */ + { + Operand res = context.AddIntrinsic(Intrinsic.X86Haddpd, GetVec(op.Rn), GetVec(op.Rn)); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPAdd), op1, op2); + }); + } + } + + public static void Faddp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FaddpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorPairwiseOpF(context, (op1, op2) => + { + return EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + Intrinsic addInst = (op.Size & 1) == 0 ? Intrinsic.X86Addps : Intrinsic.X86Addpd; + + return context.AddIntrinsic(addInst, op1, op2); + }, scalar: false, op1, op2); + }); + } + else + { + EmitVectorPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPAdd), op1, op2); + }); + } + } + + public static void Fdiv_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FdivS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF(context, Intrinsic.X86Divss, Intrinsic.X86Divsd); + } + else if (Optimizations.FastFP) + { + EmitScalarBinaryOpF(context, (op1, op2) => context.Divide(op1, op2)); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPDiv), op1, op2); + }); + } + } + + public static void Fdiv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FdivV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF(context, Intrinsic.X86Divps, Intrinsic.X86Divpd); + } + else if (Optimizations.FastFP) + { + EmitVectorBinaryOpF(context, (op1, op2) => context.Divide(op1, op2)); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPDiv), op1, op2); + }); + } + } + + public static void Fmadd_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarTernaryOpF(context, Intrinsic.Arm64FmaddS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand a = GetVec(op.Ra); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res; + + if (op.Size == 0) + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ss, a, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); + res = context.AddIntrinsic(Intrinsic.X86Addss, a, res); + } + + context.Copy(d, context.VectorZeroUpper96(res)); + } + else /* if (op.Size == 1) */ + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfmadd231sd, a, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Addsd, a, res); + } + + context.Copy(d, context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarTernaryRaOpF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulAdd), op1, op2, op3); + }); + } + } + + public static void Fmax_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FmaxS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true); + }, scalar: true); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMax), op1, op2); + }); + } + } + + public static void Fmax_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FmaxV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true); + }, scalar: false); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMax), op1, op2); + }); + } + } + + public static void Fmaxnm_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FmaxnmS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF(context, isMaxNum: true, scalar: true); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMaxNum), op1, op2); + }); + } + } + + public static void Fmaxnm_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FmaxnmV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF(context, isMaxNum: true, scalar: false); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMaxNum), op1, op2); + }); + } + } + + public static void Fmaxnmp_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FmaxnmpS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2ScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSse41MaxMinNumOpF(context, isMaxNum: true, scalar: true, op1, op2); + }); + } + else + { + EmitScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMaxNum), op1, op2); + }); + } + } + + public static void Fmaxnmp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FmaxnmpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorPairwiseOpF(context, (op1, op2) => + { + return EmitSse41MaxMinNumOpF(context, isMaxNum: true, scalar: false, op1, op2); + }); + } + else + { + EmitVectorPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMaxNum), op1, op2); + }); + } + } + + public static void Fmaxnmv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FmaxnmvV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSse41MaxMinNumOpF(context, isMaxNum: true, scalar: false, op1, op2); + }); + } + else + { + EmitVectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMaxNum), op1, op2); + }); + } + } + + public static void Fmaxp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FmaxpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorPairwiseOpF(context, (op1, op2) => + { + return EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true); + }, scalar: false, op1, op2); + }); + } + else + { + EmitVectorPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMax), op1, op2); + }); + } + } + + public static void Fmaxv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FmaxvV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: true); + }, scalar: false, op1, op2); + }); + } + else + { + EmitVectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMax), op1, op2); + }); + } + } + + public static void Fmin_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FminS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false); + }, scalar: true); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMin), op1, op2); + }); + } + } + + public static void Fmin_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FminV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false); + }, scalar: false); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMin), op1, op2); + }); + } + } + + public static void Fminnm_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FminnmS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF(context, isMaxNum: false, scalar: true); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMinNum), op1, op2); + }); + } + } + + public static void Fminnm_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FminnmV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF(context, isMaxNum: false, scalar: false); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMinNum), op1, op2); + }); + } + } + + public static void Fminnmp_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FminnmpS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2ScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSse41MaxMinNumOpF(context, isMaxNum: false, scalar: true, op1, op2); + }); + } + else + { + EmitScalarPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMinNum), op1, op2); + }); + } + } + + public static void Fminnmp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FminnmpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorPairwiseOpF(context, (op1, op2) => + { + return EmitSse41MaxMinNumOpF(context, isMaxNum: false, scalar: false, op1, op2); + }); + } + else + { + EmitVectorPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMinNum), op1, op2); + }); + } + } + + public static void Fminnmv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FminnmvV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSse41MaxMinNumOpF(context, isMaxNum: false, scalar: false, op1, op2); + }); + } + else + { + EmitVectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMinNum), op1, op2); + }); + } + } + + public static void Fminp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FminpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorPairwiseOpF(context, (op1, op2) => + { + return EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false); + }, scalar: false, op1, op2); + }); + } + else + { + EmitVectorPairwiseOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMin), op1, op2); + }); + } + } + + public static void Fminv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FminvV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse2VectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: false); + }, scalar: false, op1, op2); + }); + } + else + { + EmitVectorAcrossVectorOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMin), op1, op2); + }); + } + } + + public static void Fmla_Se(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarTernaryOpFRdByElem(context, Intrinsic.Arm64FmlaSe); + } + else if (Optimizations.UseFma) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + int shuffleMask = op.Index | op.Index << 2 | op.Index << 4 | op.Index << 6; + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask)); + + res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ss, d, n, res); + + context.Copy(d, context.VectorZeroUpper96(res)); + } + else /* if (sizeF == 1) */ + { + int shuffleMask = op.Index | op.Index << 1; + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask)); + + res = context.AddIntrinsic(Intrinsic.X86Vfmadd231sd, d, n, res); + + context.Copy(d, context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarTernaryOpByElemF(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Fmla_V(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpFRd(context, Intrinsic.Arm64FmlaV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + Operand res; + + if (sizeF == 0) + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ps, d, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m); + res = context.AddIntrinsic(Intrinsic.X86Addps, d, res); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else /* if (sizeF == 1) */ + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfmadd231pd, d, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res); + } + + context.Copy(d, res); + } + } + else + { + EmitVectorTernaryOpF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulAdd), op1, op2, op3); + }); + } + } + + public static void Fmla_Ve(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpFRdByElem(context, Intrinsic.Arm64FmlaVe); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + int shuffleMask = op.Index | op.Index << 2 | op.Index << 4 | op.Index << 6; + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask)); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfmadd231ps, d, n, res); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res); + res = context.AddIntrinsic(Intrinsic.X86Addps, d, res); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else /* if (sizeF == 1) */ + { + int shuffleMask = op.Index | op.Index << 1; + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask)); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfmadd231pd, d, n, res); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res); + res = context.AddIntrinsic(Intrinsic.X86Addpd, d, res); + } + + context.Copy(d, res); + } + } + else + { + EmitVectorTernaryOpByElemF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulAdd), op1, op2, op3); + }); + } + } + + public static void Fmls_Se(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarTernaryOpFRdByElem(context, Intrinsic.Arm64FmlsSe); + } + else if (Optimizations.UseFma) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + int shuffleMask = op.Index | op.Index << 2 | op.Index << 4 | op.Index << 6; + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask)); + + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, d, n, res); + + context.Copy(d, context.VectorZeroUpper96(res)); + } + else /* if (sizeF == 1) */ + { + int shuffleMask = op.Index | op.Index << 1; + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask)); + + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, d, n, res); + + context.Copy(d, context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarTernaryOpByElemF(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Fmls_V(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpFRd(context, Intrinsic.Arm64FmlsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + Operand res; + + if (sizeF == 0) + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, d, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subps, d, res); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else /* if (sizeF == 1) */ + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, d, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res); + } + + context.Copy(d, res); + } + } + else + { + EmitVectorTernaryOpF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulSub), op1, op2, op3); + }); + } + } + + public static void Fmls_Ve(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpFRdByElem(context, Intrinsic.Arm64FmlsVe); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + int shuffleMask = op.Index | op.Index << 2 | op.Index << 4 | op.Index << 6; + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask)); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, d, n, res); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res); + res = context.AddIntrinsic(Intrinsic.X86Subps, d, res); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else /* if (sizeF == 1) */ + { + int shuffleMask = op.Index | op.Index << 1; + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask)); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, d, n, res); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res); + res = context.AddIntrinsic(Intrinsic.X86Subpd, d, res); + } + + context.Copy(d, res); + } + } + else + { + EmitVectorTernaryOpByElemF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulSub), op1, op2, op3); + }); + } + } + + public static void Fmsub_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarTernaryOpF(context, Intrinsic.Arm64FmsubS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand a = GetVec(op.Ra); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res; + + if (op.Size == 0) + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, a, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subss, a, res); + } + + context.Copy(d, context.VectorZeroUpper96(res)); + } + else /* if (op.Size == 1) */ + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, a, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subsd, a, res); + } + + context.Copy(d, context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarTernaryRaOpF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulSub), op1, op2, op3); + }); + } + } + + public static void Fmul_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FmulS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd); + } + else if (Optimizations.FastFP) + { + EmitScalarBinaryOpF(context, (op1, op2) => context.Multiply(op1, op2)); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op1, op2); + }); + } + } + + public static void Fmul_Se(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpFByElem(context, Intrinsic.Arm64FmulSe); + } + else + { + EmitScalarBinaryOpByElemF(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Fmul_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FmulV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF(context, Intrinsic.X86Mulps, Intrinsic.X86Mulpd); + } + else if (Optimizations.FastFP) + { + EmitVectorBinaryOpF(context, (op1, op2) => context.Multiply(op1, op2)); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op1, op2); + }); + } + } + + public static void Fmul_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpFByElem(context, Intrinsic.Arm64FmulVe); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + int shuffleMask = op.Index | op.Index << 2 | op.Index << 4 | op.Index << 6; + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(shuffleMask)); + + res = context.AddIntrinsic(Intrinsic.X86Mulps, n, res); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + int shuffleMask = op.Index | op.Index << 1; + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufpd, m, m, Const(shuffleMask)); + + res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, res); + + context.Copy(GetVec(op.Rd), res); + } + } + else if (Optimizations.FastFP) + { + EmitVectorBinaryOpByElemF(context, (op1, op2) => context.Multiply(op1, op2)); + } + else + { + EmitVectorBinaryOpByElemF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op1, op2); + }); + } + } + + public static void Fmulx_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FmulxS); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulX), op1, op2); + }); + } + } + + public static void Fmulx_Se(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpFByElem(context, Intrinsic.Arm64FmulxSe); + } + else + { + EmitScalarBinaryOpByElemF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulX), op1, op2); + }); + } + } + + public static void Fmulx_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FmulxV); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulX), op1, op2); + }); + } + } + + public static void Fmulx_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpFByElem(context, Intrinsic.Arm64FmulxVe); + } + else + { + EmitVectorBinaryOpByElemF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulX), op1, op2); + }); + } + } + + public static void Fneg_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FnegS); + } + else if (Optimizations.UseSse2) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + if (op.Size == 0) + { + Operand mask = X86GetScalar(context, -0f); + + Operand res = context.AddIntrinsic(Intrinsic.X86Xorps, mask, GetVec(op.Rn)); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else /* if (op.Size == 1) */ + { + Operand mask = X86GetScalar(context, -0d); + + Operand res = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, GetVec(op.Rn)); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarUnaryOpF(context, (op1) => context.Negate(op1)); + } + } + + public static void Fneg_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FnegV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand mask = X86GetAllElements(context, -0f); + + Operand res = context.AddIntrinsic(Intrinsic.X86Xorps, mask, GetVec(op.Rn)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + Operand mask = X86GetAllElements(context, -0d); + + Operand res = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, GetVec(op.Rn)); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + EmitVectorUnaryOpF(context, (op1) => context.Negate(op1)); + } + } + + public static void Fnmadd_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarTernaryOpF(context, Intrinsic.Arm64FnmaddS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand a = GetVec(op.Ra); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res; + + if (op.Size == 0) + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmsub231ss, a, n, m); + } + else + { + Operand mask = X86GetScalar(context, -0f); + Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a); + + res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subss, aNeg, res); + } + + context.Copy(d, context.VectorZeroUpper96(res)); + } + else /* if (op.Size == 1) */ + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmsub231sd, a, n, m); + } + else + { + Operand mask = X86GetScalar(context, -0d); + Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a); + + res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subsd, aNeg, res); + } + + context.Copy(d, context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarTernaryRaOpF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPNegMulAdd), op1, op2, op3); + }); + } + } + + public static void Fnmsub_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarTernaryOpF(context, Intrinsic.Arm64FnmsubS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand a = GetVec(op.Ra); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res; + + if (op.Size == 0) + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfmsub231ss, a, n, m); + } + else + { + Operand mask = X86GetScalar(context, -0f); + Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorps, mask, a); + + res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); + res = context.AddIntrinsic(Intrinsic.X86Addss, aNeg, res); + } + + context.Copy(d, context.VectorZeroUpper96(res)); + } + else /* if (op.Size == 1) */ + { + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfmsub231sd, a, n, m); + } + else + { + Operand mask = X86GetScalar(context, -0d); + Operand aNeg = context.AddIntrinsic(Intrinsic.X86Xorpd, mask, a); + + res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Addsd, aNeg, res); + } + + context.Copy(d, context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarTernaryRaOpF(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPNegMulSub), op1, op2, op3); + }); + } + } + + public static void Fnmul_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FnmulS); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => context.Negate(context.Multiply(op1, op2))); + } + } + + public static void Frecpe_S(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrecpeS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0) + { + Operand res = EmitSse41Round32Exp8OpF(context, context.AddIntrinsic(Intrinsic.X86Rcpss, GetVec(op.Rn)), scalar: true); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRecipEstimate), op1); + }); + } + } + + public static void Frecpe_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrecpeV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0) + { + Operand res = EmitSse41Round32Exp8OpF(context, context.AddIntrinsic(Intrinsic.X86Rcpps, GetVec(op.Rn)), scalar: false); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRecipEstimate), op1); + }); + } + } + + public static void Frecps_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FrecpsS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + Operand res; + + if (sizeF == 0) + { + Operand mask = X86GetScalar(context, 2f); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, mask, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subss, mask, res); + } + + res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: true, sizeF); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else /* if (sizeF == 1) */ + { + Operand mask = X86GetScalar(context, 2d); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, mask, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subsd, mask, res); + } + + res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: true, sizeF); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRecipStepFused), op1, op2); + }); + } + } + + public static void Frecps_V(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FrecpsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + Operand res; + + if (sizeF == 0) + { + Operand mask = X86GetAllElements(context, 2f); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, mask, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subps, mask, res); + } + + res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + Operand mask = X86GetAllElements(context, 2d); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, mask, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subpd, mask, res); + } + + res = EmitSse41RecipStepSelectOpF(context, n, m, res, mask, scalar: false, sizeF); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRecipStepFused), op1, op2); + }); + } + } + + public static void Frecpx_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FrecpxS); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRecpX), op1); + }); + } + } + + public static void Frinta_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintaS); + } + else if (Optimizations.UseSse41) + { + EmitSse41ScalarRoundOpF(context, FPRoundingMode.ToNearestAway); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1); + }); + } + } + + public static void Frinta_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintaV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorRoundOpF(context, FPRoundingMode.ToNearestAway); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1); + }); + } + } + + public static void Frinti_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintiS); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitRoundByRMode(context, op1); + }); + } + } + + public static void Frinti_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintiV); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitRoundByRMode(context, op1); + }); + } + } + + public static void Frintm_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintmS); + } + else if (Optimizations.UseSse41) + { + EmitSse41ScalarRoundOpF(context, FPRoundingMode.TowardsMinusInfinity); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Floor), op1); + }); + } + } + + public static void Frintm_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintmV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorRoundOpF(context, FPRoundingMode.TowardsMinusInfinity); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Floor), op1); + }); + } + } + + public static void Frintn_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintnS); + } + else if (Optimizations.UseSse41) + { + EmitSse41ScalarRoundOpF(context, FPRoundingMode.ToNearest); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitRoundMathCall(context, MidpointRounding.ToEven, op1); + }); + } + } + + public static void Frintn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintnV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorRoundOpF(context, FPRoundingMode.ToNearest); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitRoundMathCall(context, MidpointRounding.ToEven, op1); + }); + } + } + + public static void Frintp_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintpS); + } + else if (Optimizations.UseSse41) + { + EmitSse41ScalarRoundOpF(context, FPRoundingMode.TowardsPlusInfinity); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Ceiling), op1); + }); + } + } + + public static void Frintp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintpV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorRoundOpF(context, FPRoundingMode.TowardsPlusInfinity); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Ceiling), op1); + }); + } + } + + public static void Frintx_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintxS); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitRoundByRMode(context, op1); + }); + } + } + + public static void Frintx_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintxV); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitRoundByRMode(context, op1); + }); + } + } + + public static void Frintz_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrintzS); + } + else if (Optimizations.UseSse41) + { + EmitSse41ScalarRoundOpF(context, FPRoundingMode.TowardsZero); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Truncate), op1); + }); + } + } + + public static void Frintz_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrintzV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorRoundOpF(context, FPRoundingMode.TowardsZero); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitUnaryMathCall(context, nameof(Math.Truncate), op1); + }); + } + } + + public static void Frsqrte_S(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FrsqrteS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0) + { + Operand res = EmitSse41Round32Exp8OpF(context, context.AddIntrinsic(Intrinsic.X86Rsqrtss, GetVec(op.Rn)), scalar: true); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRSqrtEstimate), op1); + }); + } + } + + public static void Frsqrte_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FrsqrteV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0) + { + Operand res = EmitSse41Round32Exp8OpF(context, context.AddIntrinsic(Intrinsic.X86Rsqrtps, GetVec(op.Rn)), scalar: false); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRSqrtEstimate), op1); + }); + } + } + + public static void Frsqrts_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FrsqrtsS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + Operand res; + + if (sizeF == 0) + { + Operand maskHalf = X86GetScalar(context, 0.5f); + Operand maskThree = X86GetScalar(context, 3f); + Operand maskOneHalf = X86GetScalar(context, 1.5f); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ss, maskThree, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subss, maskThree, res); + } + + res = context.AddIntrinsic(Intrinsic.X86Mulss, maskHalf, res); + res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: true, sizeF); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + else /* if (sizeF == 1) */ + { + Operand maskHalf = X86GetScalar(context, 0.5d); + Operand maskThree = X86GetScalar(context, 3d); + Operand maskOneHalf = X86GetScalar(context, 1.5d); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231sd, maskThree, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subsd, maskThree, res); + } + + res = context.AddIntrinsic(Intrinsic.X86Mulsd, maskHalf, res); + res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: true, sizeF); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper64(res)); + } + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRSqrtStepFused), op1, op2); + }); + } + } + + public static void Frsqrts_V(ArmEmitterContext context) // Fused. + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FrsqrtsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int sizeF = op.Size & 1; + + Operand res; + + if (sizeF == 0) + { + Operand maskHalf = X86GetAllElements(context, 0.5f); + Operand maskThree = X86GetAllElements(context, 3f); + Operand maskOneHalf = X86GetAllElements(context, 1.5f); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231ps, maskThree, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subps, maskThree, res); + } + + res = context.AddIntrinsic(Intrinsic.X86Mulps, maskHalf, res); + res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: false, sizeF); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + Operand maskHalf = X86GetAllElements(context, 0.5d); + Operand maskThree = X86GetAllElements(context, 3d); + Operand maskOneHalf = X86GetAllElements(context, 1.5d); + + if (Optimizations.UseFma) + { + res = context.AddIntrinsic(Intrinsic.X86Vfnmadd231pd, maskThree, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Subpd, maskThree, res); + } + + res = context.AddIntrinsic(Intrinsic.X86Mulpd, maskHalf, res); + res = EmitSse41RecipStepSelectOpF(context, n, m, res, maskOneHalf, scalar: false, sizeF); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRSqrtStepFused), op1, op2); + }); + } + } + + public static void Fsqrt_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FsqrtS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarUnaryOpF(context, Intrinsic.X86Sqrtss, Intrinsic.X86Sqrtsd); + } + else + { + EmitScalarUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPSqrt), op1); + }); + } + } + + public static void Fsqrt_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FsqrtV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorUnaryOpF(context, Intrinsic.X86Sqrtps, Intrinsic.X86Sqrtpd); + } + else + { + EmitVectorUnaryOpF(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPSqrt), op1); + }); + } + } + + public static void Fsub_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOpF(context, Intrinsic.Arm64FsubS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF(context, Intrinsic.X86Subss, Intrinsic.X86Subsd); + } + else if (Optimizations.FastFP) + { + EmitScalarBinaryOpF(context, (op1, op2) => context.Subtract(op1, op2)); + } + else + { + EmitScalarBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), op1, op2); + }); + } + } + + public static void Fsub_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpF(context, Intrinsic.Arm64FsubV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF(context, Intrinsic.X86Subps, Intrinsic.X86Subpd); + } + else if (Optimizations.FastFP) + { + EmitVectorBinaryOpF(context, (op1, op2) => context.Subtract(op1, op2)); + } + else + { + EmitVectorBinaryOpF(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), op1, op2); + }); + } + } + + public static void Mla_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64MlaV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorMul_AddSub(context, AddSub.Add); + } + else + { + EmitVectorTernaryOpZx(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Mla_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRdByElem(context, Intrinsic.Arm64MlaVe); + } + else + { + EmitVectorTernaryOpByElemZx(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Mls_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64MlsV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorMul_AddSub(context, AddSub.Subtract); + } + else + { + EmitVectorTernaryOpZx(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Mls_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRdByElem(context, Intrinsic.Arm64MlsVe); + } + else + { + EmitVectorTernaryOpByElemZx(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Mul_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64MulV); + } + else if (Optimizations.UseSse41) + { + EmitSse41VectorMul_AddSub(context, AddSub.None); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Mul_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpByElem(context, Intrinsic.Arm64MulVe); + } + else + { + EmitVectorBinaryOpByElemZx(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Neg_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOp(context, Intrinsic.Arm64NegS); + } + else + { + EmitScalarUnaryOpSx(context, (op1) => context.Negate(op1)); + } + } + + public static void Neg_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64NegV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Intrinsic subInst = X86PsubInstruction[op.Size]; + + Operand res = context.AddIntrinsic(subInst, context.VectorZero(), GetVec(op.Rn)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorUnaryOpSx(context, (op1) => context.Negate(op1)); + } + } + + public static void Pmull_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseArm64Pmull) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64PmullV); + } + else if (Optimizations.UsePclmulqdq && op.Size == 3) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + int imm8 = op.RegisterSize == RegisterSize.Simd64 ? 0b0000_0000 : 0b0001_0001; + + Operand res = context.AddIntrinsic(Intrinsic.X86Pclmulqdq, n, m, Const(imm8)); + + context.Copy(GetVec(op.Rd), res); + } + else if (Optimizations.UseSse41) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd64) + { + n = context.VectorZeroUpper64(n); + m = context.VectorZeroUpper64(m); + } + else /* if (op.RegisterSize == RegisterSize.Simd128) */ + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Operand res = context.VectorZero(); + + if (op.Size == 0) + { + n = context.AddIntrinsic(Intrinsic.X86Pmovzxbw, n); + m = context.AddIntrinsic(Intrinsic.X86Pmovzxbw, m); + + for (int i = 0; i < 8; i++) + { + Operand mask = context.AddIntrinsic(Intrinsic.X86Psllw, n, Const(15 - i)); + mask = context.AddIntrinsic(Intrinsic.X86Psraw, mask, Const(15)); + + Operand tmp = context.AddIntrinsic(Intrinsic.X86Psllw, m, Const(i)); + tmp = context.AddIntrinsic(Intrinsic.X86Pand, tmp, mask); + + res = context.AddIntrinsic(Intrinsic.X86Pxor, res, tmp); + } + } + else /* if (op.Size == 3) */ + { + Operand zero = context.VectorZero(); + + for (int i = 0; i < 64; i++) + { + Operand mask = context.AddIntrinsic(Intrinsic.X86Movlhps, n, n); + mask = context.AddIntrinsic(Intrinsic.X86Psllq, mask, Const(63 - i)); + mask = context.AddIntrinsic(Intrinsic.X86Psrlq, mask, Const(63)); + mask = context.AddIntrinsic(Intrinsic.X86Psubq, zero, mask); + + Operand tmp = EmitSse2Sll_128(context, m, i); + tmp = context.AddIntrinsic(Intrinsic.X86Pand, tmp, mask); + + res = context.AddIntrinsic(Intrinsic.X86Pxor, res, tmp); + } + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res; + + if (op.Size == 0) + { + res = context.VectorZero(); + + int part = op.RegisterSize == RegisterSize.Simd64 ? 0 : 8; + + for (int index = 0; index < 8; index++) + { + Operand ne = context.VectorExtract8(n, part + index); + Operand me = context.VectorExtract8(m, part + index); + + Operand de = EmitPolynomialMultiply(context, ne, me, 8); + + res = EmitVectorInsert(context, res, de, index, 1); + } + } + else /* if (op.Size == 3) */ + { + int part = op.RegisterSize == RegisterSize.Simd64 ? 0 : 1; + + Operand ne = context.VectorExtract(OperandType.I64, n, part); + Operand me = context.VectorExtract(OperandType.I64, m, part); + + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.PolynomialMult64_128)), ne, me); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Raddhn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64RaddhnV); + } + else + { + EmitHighNarrow(context, (op1, op2) => context.Add(op1, op2), round: true); + } + } + + public static void Rsubhn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64RsubhnV); + } + else + { + EmitHighNarrow(context, (op1, op2) => context.Subtract(op1, op2), round: true); + } + } + + public static void Saba_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64SabaV); + } + else + { + EmitVectorTernaryOpSx(context, (op1, op2, op3) => + { + return context.Add(op1, EmitAbs(context, context.Subtract(op2, op3))); + }); + } + } + + public static void Sabal_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64SabalV); + } + else + { + EmitVectorWidenRnRmTernaryOpSx(context, (op1, op2, op3) => + { + return context.Add(op1, EmitAbs(context, context.Subtract(op2, op3))); + }); + } + } + + public static void Sabd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SabdV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + EmitSse41VectorSabdOp(context, op, n, m, isLong: false); + } + else + { + EmitVectorBinaryOpSx(context, (op1, op2) => + { + return EmitAbs(context, context.Subtract(op1, op2)); + }); + } + } + + public static void Sabdl_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SabdlV); + } + else if (Optimizations.UseSse41 && op.Size < 2) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = op.Size == 0 + ? Intrinsic.X86Pmovsxbw + : Intrinsic.X86Pmovsxwd; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + EmitSse41VectorSabdOp(context, op, n, m, isLong: true); + } + else + { + EmitVectorWidenRnRmBinaryOpSx(context, (op1, op2) => + { + return EmitAbs(context, context.Subtract(op1, op2)); + }); + } + } + + public static void Sadalp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpRd(context, Intrinsic.Arm64SadalpV); + } + else + { + EmitAddLongPairwise(context, signed: true, accumulate: true); + } + } + + public static void Saddl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SaddlV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovsxInstruction[op.Size]; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic addInst = X86PaddInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(addInst, n, m)); + } + else + { + EmitVectorWidenRnRmBinaryOpSx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Saddlp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64SaddlpV); + } + else + { + EmitAddLongPairwise(context, signed: true, accumulate: false); + } + } + + public static void Saddlv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64SaddlvV); + } + else + { + EmitVectorLongAcrossVectorOpSx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Saddw_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SaddwV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovsxInstruction[op.Size]; + + m = context.AddIntrinsic(movInst, m); + + Intrinsic addInst = X86PaddInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(addInst, n, m)); + } + else + { + EmitVectorWidenRmBinaryOpSx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Shadd_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64ShaddV); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pand, n, m); + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pxor, n, m); + + Intrinsic shiftInst = op.Size == 1 ? Intrinsic.X86Psraw : Intrinsic.X86Psrad; + + res2 = context.AddIntrinsic(shiftInst, res2, Const(1)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, res2); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpSx(context, (op1, op2) => + { + return context.ShiftRightSI(context.Add(op1, op2), Const(1)); + }); + } + } + + public static void Shsub_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64ShsubV); + } + else if (Optimizations.UseSse2 && op.Size < 2) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand mask = X86GetAllElements(context, (int)(op.Size == 0 ? 0x80808080u : 0x80008000u)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + Operand nPlusMask = context.AddIntrinsic(addInst, n, mask); + Operand mPlusMask = context.AddIntrinsic(addInst, m, mask); + + Intrinsic avgInst = op.Size == 0 ? Intrinsic.X86Pavgb : Intrinsic.X86Pavgw; + + Operand res = context.AddIntrinsic(avgInst, nPlusMask, mPlusMask); + + Intrinsic subInst = X86PsubInstruction[op.Size]; + + res = context.AddIntrinsic(subInst, nPlusMask, res); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpSx(context, (op1, op2) => + { + return context.ShiftRightSI(context.Subtract(op1, op2), Const(1)); + }); + } + } + + public static void Smax_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SmaxV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic maxInst = X86PmaxsInstruction[op.Size]; + + Operand res = context.AddIntrinsic(maxInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpSx(context, (op1, op2) => EmitMax64Op(context, op1, op2, signed: true)); + } + } + + public static void Smaxp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SmaxpV); + } + else if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PmaxsInstruction); + } + else + { + EmitVectorPairwiseOpSx(context, (op1, op2) => EmitMax64Op(context, op1, op2, signed: true)); + } + } + + public static void Smaxv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64SmaxvV); + } + else + { + EmitVectorAcrossVectorOpSx(context, (op1, op2) => EmitMax64Op(context, op1, op2, signed: true)); + } + } + + public static void Smin_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SminV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic minInst = X86PminsInstruction[op.Size]; + + Operand res = context.AddIntrinsic(minInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpSx(context, (op1, op2) => EmitMin64Op(context, op1, op2, signed: true)); + } + } + + public static void Sminp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SminpV); + } + else if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PminsInstruction); + } + else + { + EmitVectorPairwiseOpSx(context, (op1, op2) => EmitMin64Op(context, op1, op2, signed: true)); + } + } + + public static void Sminv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64SminvV); + } + else + { + EmitVectorAcrossVectorOpSx(context, (op1, op2) => EmitMin64Op(context, op1, op2, signed: true)); + } + } + + public static void Smlal_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64SmlalV); + } + else if (Optimizations.UseSse41 && op.Size < 2) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovsxInstruction[op.Size]; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic mullInst = op.Size == 0 ? Intrinsic.X86Pmullw : Intrinsic.X86Pmulld; + + Operand res = context.AddIntrinsic(mullInst, n, m); + + Intrinsic addInst = X86PaddInstruction[op.Size + 1]; + + context.Copy(d, context.AddIntrinsic(addInst, d, res)); + } + else + { + EmitVectorWidenRnRmTernaryOpSx(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Smlal_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRdByElem(context, Intrinsic.Arm64SmlalVe); + } + else + { + EmitVectorWidenTernaryOpByElemSx(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Smlsl_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64SmlslV); + } + else if (Optimizations.UseSse41 && op.Size < 2) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = op.Size == 0 ? Intrinsic.X86Pmovsxbw : Intrinsic.X86Pmovsxwd; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic mullInst = op.Size == 0 ? Intrinsic.X86Pmullw : Intrinsic.X86Pmulld; + + Operand res = context.AddIntrinsic(mullInst, n, m); + + Intrinsic subInst = X86PsubInstruction[op.Size + 1]; + + context.Copy(d, context.AddIntrinsic(subInst, d, res)); + } + else + { + EmitVectorWidenRnRmTernaryOpSx(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Smlsl_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRdByElem(context, Intrinsic.Arm64SmlslVe); + } + else + { + EmitVectorWidenTernaryOpByElemSx(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Smull_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SmullV); + } + else + { + EmitVectorWidenRnRmBinaryOpSx(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Smull_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpByElem(context, Intrinsic.Arm64SmullVe); + } + else + { + EmitVectorWidenBinaryOpByElemSx(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Sqabs_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingUnaryOp(context, Intrinsic.Arm64SqabsS); + } + else + { + EmitScalarSaturatingUnaryOpSx(context, (op1) => EmitAbs(context, op1)); + } + } + + public static void Sqabs_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingUnaryOp(context, Intrinsic.Arm64SqabsV); + } + else + { + EmitVectorSaturatingUnaryOpSx(context, (op1) => EmitAbs(context, op1)); + } + } + + public static void Sqadd_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOp(context, Intrinsic.Arm64SqaddS); + } + else + { + EmitScalarSaturatingBinaryOpSx(context, flags: SaturatingFlags.Add); + } + } + + public static void Sqadd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64SqaddV); + } + else + { + EmitVectorSaturatingBinaryOpSx(context, flags: SaturatingFlags.Add); + } + } + + public static void Sqdmulh_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOp(context, Intrinsic.Arm64SqdmulhS); + } + else + { + EmitScalarSaturatingBinaryOpSx(context, (op1, op2) => EmitDoublingMultiplyHighHalf(context, op1, op2, round: false)); + } + } + + public static void Sqdmulh_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64SqdmulhV); + } + else + { + EmitVectorSaturatingBinaryOpSx(context, (op1, op2) => EmitDoublingMultiplyHighHalf(context, op1, op2, round: false)); + } + } + + public static void Sqdmulh_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOpByElem(context, Intrinsic.Arm64SqdmulhVe); + } + else + { + EmitVectorSaturatingBinaryOpByElemSx(context, (op1, op2) => EmitDoublingMultiplyHighHalf(context, op1, op2, round: false)); + } + } + + public static void Sqneg_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingUnaryOp(context, Intrinsic.Arm64SqnegS); + } + else + { + EmitScalarSaturatingUnaryOpSx(context, (op1) => context.Negate(op1)); + } + } + + public static void Sqneg_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingUnaryOp(context, Intrinsic.Arm64SqnegV); + } + else + { + EmitVectorSaturatingUnaryOpSx(context, (op1) => context.Negate(op1)); + } + } + + public static void Sqrdmulh_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOp(context, Intrinsic.Arm64SqrdmulhS); + } + else + { + EmitScalarSaturatingBinaryOpSx(context, (op1, op2) => EmitDoublingMultiplyHighHalf(context, op1, op2, round: true)); + } + } + + public static void Sqrdmulh_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64SqrdmulhV); + } + else + { + EmitVectorSaturatingBinaryOpSx(context, (op1, op2) => EmitDoublingMultiplyHighHalf(context, op1, op2, round: true)); + } + } + + public static void Sqrdmulh_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOpByElem(context, Intrinsic.Arm64SqrdmulhVe); + } + else + { + EmitVectorSaturatingBinaryOpByElemSx(context, (op1, op2) => EmitDoublingMultiplyHighHalf(context, op1, op2, round: true)); + } + } + + public static void Sqsub_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOp(context, Intrinsic.Arm64SqsubS); + } + else + { + EmitScalarSaturatingBinaryOpSx(context, flags: SaturatingFlags.Sub); + } + } + + public static void Sqsub_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64SqsubV); + } + else + { + EmitVectorSaturatingBinaryOpSx(context, flags: SaturatingFlags.Sub); + } + } + + public static void Sqxtn_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOpRd(context, Intrinsic.Arm64SqxtnS); + } + else + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.ScalarSxSx); + } + } + + public static void Sqxtn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOpRd(context, Intrinsic.Arm64SqxtnV); + } + else + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.VectorSxSx); + } + } + + public static void Sqxtun_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOpRd(context, Intrinsic.Arm64SqxtunS); + } + else + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.ScalarSxZx); + } + } + + public static void Sqxtun_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOpRd(context, Intrinsic.Arm64SqxtunV); + } + else + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.VectorSxZx); + } + } + + public static void Srhadd_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SrhaddV); + } + else if (Optimizations.UseSse2 && op.Size < 2) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand mask = X86GetAllElements(context, (int)(op.Size == 0 ? 0x80808080u : 0x80008000u)); + + Intrinsic subInst = X86PsubInstruction[op.Size]; + + Operand nMinusMask = context.AddIntrinsic(subInst, n, mask); + Operand mMinusMask = context.AddIntrinsic(subInst, m, mask); + + Intrinsic avgInst = op.Size == 0 ? Intrinsic.X86Pavgb : Intrinsic.X86Pavgw; + + Operand res = context.AddIntrinsic(avgInst, nMinusMask, mMinusMask); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, mask, res); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpSx(context, (op1, op2) => + { + Operand res = context.Add(op1, op2); + + res = context.Add(res, Const(1L)); + + return context.ShiftRightSI(res, Const(1)); + }); + } + } + + public static void Ssubl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SsublV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovsxInstruction[op.Size]; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic subInst = X86PsubInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(subInst, n, m)); + } + else + { + EmitVectorWidenRnRmBinaryOpSx(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Ssubw_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SsubwV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovsxInstruction[op.Size]; + + m = context.AddIntrinsic(movInst, m); + + Intrinsic subInst = X86PsubInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(subInst, n, m)); + } + else + { + EmitVectorWidenRmBinaryOpSx(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Sub_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOp(context, Intrinsic.Arm64SubS); + } + else + { + EmitScalarBinaryOpZx(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Sub_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SubV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic subInst = X86PsubInstruction[op.Size]; + + Operand res = context.AddIntrinsic(subInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Subhn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64SubhnV); + } + else + { + EmitHighNarrow(context, (op1, op2) => context.Subtract(op1, op2), round: false); + } + } + + public static void Suqadd_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOpRd(context, Intrinsic.Arm64SuqaddS); + } + else + { + EmitScalarSaturatingBinaryOpSx(context, flags: SaturatingFlags.Accumulate); + } + } + + public static void Suqadd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOpRd(context, Intrinsic.Arm64SuqaddV); + } + else + { + EmitVectorSaturatingBinaryOpSx(context, flags: SaturatingFlags.Accumulate); + } + } + + public static void Uaba_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64UabaV); + } + else + { + EmitVectorTernaryOpZx(context, (op1, op2, op3) => + { + return context.Add(op1, EmitAbs(context, context.Subtract(op2, op3))); + }); + } + } + + public static void Uabal_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64UabalV); + } + else + { + EmitVectorWidenRnRmTernaryOpZx(context, (op1, op2, op3) => + { + return context.Add(op1, EmitAbs(context, context.Subtract(op2, op3))); + }); + } + } + + public static void Uabd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UabdV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + EmitSse41VectorUabdOp(context, op, n, m, isLong: false); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => + { + return EmitAbs(context, context.Subtract(op1, op2)); + }); + } + } + + public static void Uabdl_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UabdlV); + } + else if (Optimizations.UseSse41 && op.Size < 2) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = op.Size == 0 + ? Intrinsic.X86Pmovzxbw + : Intrinsic.X86Pmovzxwd; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + EmitSse41VectorUabdOp(context, op, n, m, isLong: true); + } + else + { + EmitVectorWidenRnRmBinaryOpZx(context, (op1, op2) => + { + return EmitAbs(context, context.Subtract(op1, op2)); + }); + } + } + + public static void Uadalp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpRd(context, Intrinsic.Arm64UadalpV); + } + else + { + EmitAddLongPairwise(context, signed: false, accumulate: true); + } + } + + public static void Uaddl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UaddlV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovzxInstruction[op.Size]; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic addInst = X86PaddInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(addInst, n, m)); + } + else + { + EmitVectorWidenRnRmBinaryOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Uaddlp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64UaddlpV); + } + else + { + EmitAddLongPairwise(context, signed: false, accumulate: false); + } + } + + public static void Uaddlv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64UaddlvV); + } + else + { + EmitVectorLongAcrossVectorOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Uaddw_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UaddwV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovzxInstruction[op.Size]; + + m = context.AddIntrinsic(movInst, m); + + Intrinsic addInst = X86PaddInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(addInst, n, m)); + } + else + { + EmitVectorWidenRmBinaryOpZx(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Uhadd_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UhaddV); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pand, n, m); + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pxor, n, m); + + Intrinsic shiftInst = op.Size == 1 ? Intrinsic.X86Psrlw : Intrinsic.X86Psrld; + + res2 = context.AddIntrinsic(shiftInst, res2, Const(1)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, res2); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => + { + return context.ShiftRightUI(context.Add(op1, op2), Const(1)); + }); + } + } + + public static void Uhsub_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UhsubV); + } + else if (Optimizations.UseSse2 && op.Size < 2) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic avgInst = op.Size == 0 ? Intrinsic.X86Pavgb : Intrinsic.X86Pavgw; + + Operand res = context.AddIntrinsic(avgInst, n, m); + + Intrinsic subInst = X86PsubInstruction[op.Size]; + + res = context.AddIntrinsic(subInst, n, res); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => + { + return context.ShiftRightUI(context.Subtract(op1, op2), Const(1)); + }); + } + } + + public static void Umax_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UmaxV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic maxInst = X86PmaxuInstruction[op.Size]; + + Operand res = context.AddIntrinsic(maxInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => EmitMax64Op(context, op1, op2, signed: false)); + } + } + + public static void Umaxp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UmaxpV); + } + else if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PmaxuInstruction); + } + else + { + EmitVectorPairwiseOpZx(context, (op1, op2) => EmitMax64Op(context, op1, op2, signed: false)); + } + } + + public static void Umaxv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64UmaxvV); + } + else + { + EmitVectorAcrossVectorOpZx(context, (op1, op2) => EmitMax64Op(context, op1, op2, signed: false)); + } + } + + public static void Umin_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UminV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic minInst = X86PminuInstruction[op.Size]; + + Operand res = context.AddIntrinsic(minInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => EmitMin64Op(context, op1, op2, signed: false)); + } + } + + public static void Uminp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UminpV); + } + else if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp(context, X86PminuInstruction); + } + else + { + EmitVectorPairwiseOpZx(context, (op1, op2) => EmitMin64Op(context, op1, op2, signed: false)); + } + } + + public static void Uminv_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64UminvV); + } + else + { + EmitVectorAcrossVectorOpZx(context, (op1, op2) => EmitMin64Op(context, op1, op2, signed: false)); + } + } + + public static void Umlal_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64UmlalV); + } + else if (Optimizations.UseSse41 && op.Size < 2) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovzxInstruction[op.Size]; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic mullInst = op.Size == 0 ? Intrinsic.X86Pmullw : Intrinsic.X86Pmulld; + + Operand res = context.AddIntrinsic(mullInst, n, m); + + Intrinsic addInst = X86PaddInstruction[op.Size + 1]; + + context.Copy(d, context.AddIntrinsic(addInst, d, res)); + } + else + { + EmitVectorWidenRnRmTernaryOpZx(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Umlal_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRdByElem(context, Intrinsic.Arm64UmlalVe); + } + else + { + EmitVectorWidenTernaryOpByElemZx(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Umlsl_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64UmlslV); + } + else if (Optimizations.UseSse41 && op.Size < 2) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = op.Size == 0 ? Intrinsic.X86Pmovzxbw : Intrinsic.X86Pmovzxwd; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic mullInst = op.Size == 0 ? Intrinsic.X86Pmullw : Intrinsic.X86Pmulld; + + Operand res = context.AddIntrinsic(mullInst, n, m); + + Intrinsic subInst = X86PsubInstruction[op.Size + 1]; + + context.Copy(d, context.AddIntrinsic(subInst, d, res)); + } + else + { + EmitVectorWidenRnRmTernaryOpZx(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Umlsl_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRdByElem(context, Intrinsic.Arm64UmlslVe); + } + else + { + EmitVectorWidenTernaryOpByElemZx(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + } + + public static void Umull_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UmullV); + } + else + { + EmitVectorWidenRnRmBinaryOpZx(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Umull_Ve(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpByElem(context, Intrinsic.Arm64UmullVe); + } + else + { + EmitVectorWidenBinaryOpByElemZx(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Uqadd_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOp(context, Intrinsic.Arm64UqaddS); + } + else + { + EmitScalarSaturatingBinaryOpZx(context, SaturatingFlags.Add); + } + } + + public static void Uqadd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64UqaddV); + } + else + { + EmitVectorSaturatingBinaryOpZx(context, SaturatingFlags.Add); + } + } + + public static void Uqsub_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOp(context, Intrinsic.Arm64UqsubS); + } + else + { + EmitScalarSaturatingBinaryOpZx(context, SaturatingFlags.Sub); + } + } + + public static void Uqsub_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64UqsubV); + } + else + { + EmitVectorSaturatingBinaryOpZx(context, SaturatingFlags.Sub); + } + } + + public static void Uqxtn_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOpRd(context, Intrinsic.Arm64UqxtnS); + } + else + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.ScalarZxZx); + } + } + + public static void Uqxtn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOpRd(context, Intrinsic.Arm64UqxtnV); + } + else + { + EmitSaturatingNarrowOp(context, SaturatingNarrowFlags.VectorZxZx); + } + } + + public static void Urhadd_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UrhaddV); + } + else if (Optimizations.UseSse2 && op.Size < 2) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic avgInst = op.Size == 0 ? Intrinsic.X86Pavgb : Intrinsic.X86Pavgw; + + Operand res = context.AddIntrinsic(avgInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => + { + Operand res = context.Add(op1, op2); + + res = context.Add(res, Const(1L)); + + return context.ShiftRightUI(res, Const(1)); + }); + } + } + + public static void Usqadd_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarSaturatingBinaryOpRd(context, Intrinsic.Arm64UsqaddS); + } + else + { + EmitScalarSaturatingBinaryOpZx(context, SaturatingFlags.Accumulate); + } + } + + public static void Usqadd_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOpRd(context, Intrinsic.Arm64UsqaddV); + } + else + { + EmitVectorSaturatingBinaryOpZx(context, SaturatingFlags.Accumulate); + } + } + + public static void Usubl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UsublV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovzxInstruction[op.Size]; + + n = context.AddIntrinsic(movInst, n); + m = context.AddIntrinsic(movInst, m); + + Intrinsic subInst = X86PsubInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(subInst, n, m)); + } + else + { + EmitVectorWidenRnRmBinaryOpZx(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Usubw_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UsubwV); + } + else if (Optimizations.UseSse41) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + m = context.AddIntrinsic(Intrinsic.X86Psrldq, m, Const(8)); + } + + Intrinsic movInst = X86PmovzxInstruction[op.Size]; + + m = context.AddIntrinsic(movInst, m); + + Intrinsic subInst = X86PsubInstruction[op.Size + 1]; + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(subInst, n, m)); + } + else + { + EmitVectorWidenRmBinaryOpZx(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + private static Operand EmitAbs(ArmEmitterContext context, Operand value) + { + Operand isPositive = context.ICompareGreaterOrEqual(value, Const(value.Type, 0)); + + return context.ConditionalSelect(isPositive, value, context.Negate(value)); + } + + private static void EmitAddLongPairwise(ArmEmitterContext context, bool signed, bool accumulate) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int pairs = op.GetPairsCount() >> op.Size; + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + + Operand ne0 = EmitVectorExtract(context, op.Rn, pairIndex, op.Size, signed); + Operand ne1 = EmitVectorExtract(context, op.Rn, pairIndex + 1, op.Size, signed); + + Operand e = context.Add(ne0, ne1); + + if (accumulate) + { + Operand de = EmitVectorExtract(context, op.Rd, index, op.Size + 1, signed); + + e = context.Add(e, de); + } + + res = EmitVectorInsert(context, res, e, index, op.Size + 1); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static Operand EmitDoublingMultiplyHighHalf( + ArmEmitterContext context, + Operand n, + Operand m, + bool round) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + int eSize = 8 << op.Size; + + Operand res = context.Multiply(n, m); + + if (!round) + { + res = context.ShiftRightSI(res, Const(eSize - 1)); + } + else + { + long roundConst = 1L << (eSize - 1); + + res = context.ShiftLeft(res, Const(1)); + + res = context.Add(res, Const(roundConst)); + + res = context.ShiftRightSI(res, Const(eSize)); + + Operand isIntMin = context.ICompareEqual(res, Const((long)int.MinValue)); + + res = context.ConditionalSelect(isIntMin, context.Negate(res), res); + } + + return res; + } + + private static void EmitHighNarrow(ArmEmitterContext context, Func2I emit, bool round) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + int elems = 8 >> op.Size; + int eSize = 8 << op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); + + long roundConst = 1L << (eSize - 1); + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size + 1); + Operand me = EmitVectorExtractZx(context, op.Rm, index, op.Size + 1); + + Operand de = emit(ne, me); + + if (round) + { + de = context.Add(de, Const(roundConst)); + } + + de = context.ShiftRightUI(de, Const(eSize)); + + res = EmitVectorInsert(context, res, de, part + index, op.Size); + } + + context.Copy(d, res); + } + + private static Operand EmitMax64Op(ArmEmitterContext context, Operand op1, Operand op2, bool signed) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand cmp = signed + ? context.ICompareGreaterOrEqual (op1, op2) + : context.ICompareGreaterOrEqualUI(op1, op2); + + return context.ConditionalSelect(cmp, op1, op2); + } + + private static Operand EmitMin64Op(ArmEmitterContext context, Operand op1, Operand op2, bool signed) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand cmp = signed + ? context.ICompareLessOrEqual (op1, op2) + : context.ICompareLessOrEqualUI(op1, op2); + + return context.ConditionalSelect(cmp, op1, op2); + } + + private static void EmitSse41ScalarRoundOpF(ArmEmitterContext context, FPRoundingMode roundMode) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand res; + + if (roundMode != FPRoundingMode.ToNearestAway) + { + Intrinsic inst = (op.Size & 1) != 0 ? Intrinsic.X86Roundsd : Intrinsic.X86Roundss; + + res = context.AddIntrinsic(inst, n, Const(X86GetRoundControl(roundMode))); + } + else + { + res = EmitSse41RoundToNearestWithTiesToAwayOpF(context, n, scalar: true); + } + + if ((op.Size & 1) != 0) + { + res = context.VectorZeroUpper64(res); + } + else + { + res = context.VectorZeroUpper96(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitSse41VectorRoundOpF(ArmEmitterContext context, FPRoundingMode roundMode) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand res; + + if (roundMode != FPRoundingMode.ToNearestAway) + { + Intrinsic inst = (op.Size & 1) != 0 ? Intrinsic.X86Roundpd : Intrinsic.X86Roundps; + + res = context.AddIntrinsic(inst, n, Const(X86GetRoundControl(roundMode))); + } + else + { + res = EmitSse41RoundToNearestWithTiesToAwayOpF(context, n, scalar: false); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static Operand EmitSse41Round32Exp8OpF(ArmEmitterContext context, Operand value, bool scalar) + { + Operand roundMask; + Operand truncMask; + Operand expMask; + + if (scalar) + { + roundMask = X86GetScalar(context, 0x4000); + truncMask = X86GetScalar(context, unchecked((int)0xFFFF8000)); + expMask = X86GetScalar(context, 0x7F800000); + } + else + { + roundMask = X86GetAllElements(context, 0x4000); + truncMask = X86GetAllElements(context, unchecked((int)0xFFFF8000)); + expMask = X86GetAllElements(context, 0x7F800000); + } + + Operand oValue = value; + Operand masked = context.AddIntrinsic(Intrinsic.X86Pand, value, expMask); + Operand isNaNInf = context.AddIntrinsic(Intrinsic.X86Pcmpeqd, masked, expMask); + + value = context.AddIntrinsic(Intrinsic.X86Paddd, value, roundMask); + value = context.AddIntrinsic(Intrinsic.X86Pand, value, truncMask); + + return context.AddIntrinsic(Intrinsic.X86Blendvps, value, oValue, isNaNInf); + } + + private static Operand EmitSse41RecipStepSelectOpF( + ArmEmitterContext context, + Operand n, + Operand m, + Operand res, + Operand mask, + bool scalar, + int sizeF) + { + Intrinsic cmpOp; + Intrinsic shlOp; + Intrinsic blendOp; + Operand zero = context.VectorZero(); + Operand expMask; + + if (sizeF == 0) + { + cmpOp = Intrinsic.X86Pcmpeqd; + shlOp = Intrinsic.X86Pslld; + blendOp = Intrinsic.X86Blendvps; + expMask = scalar ? X86GetScalar(context, 0x7F800000 << 1) : X86GetAllElements(context, 0x7F800000 << 1); + } + else /* if (sizeF == 1) */ + { + cmpOp = Intrinsic.X86Pcmpeqq; + shlOp = Intrinsic.X86Psllq; + blendOp = Intrinsic.X86Blendvpd; + expMask = scalar ? X86GetScalar(context, 0x7FF0000000000000L << 1) : X86GetAllElements(context, 0x7FF0000000000000L << 1); + } + + n = context.AddIntrinsic(shlOp, n, Const(1)); + m = context.AddIntrinsic(shlOp, m, Const(1)); + + Operand nZero = context.AddIntrinsic(cmpOp, n, zero); + Operand mZero = context.AddIntrinsic(cmpOp, m, zero); + Operand nInf = context.AddIntrinsic(cmpOp, n, expMask); + Operand mInf = context.AddIntrinsic(cmpOp, m, expMask); + + Operand nmZero = context.AddIntrinsic(Intrinsic.X86Por, nZero, mZero); + Operand nmInf = context.AddIntrinsic(Intrinsic.X86Por, nInf, mInf); + Operand nmZeroInf = context.AddIntrinsic(Intrinsic.X86Pand, nmZero, nmInf); + + return context.AddIntrinsic(blendOp, res, mask, nmZeroInf); + } + + public static void EmitSse2VectorIsNaNOpF( + ArmEmitterContext context, + Operand opF, + out Operand qNaNMask, + out Operand sNaNMask, + bool? isQNaN = null) + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + if ((op.Size & 1) == 0) + { + const int QBit = 22; + + Operand qMask = X86GetAllElements(context, 1 << QBit); + + Operand mask1 = context.AddIntrinsic(Intrinsic.X86Cmpps, opF, opF, Const((int)CmpCondition.UnorderedQ)); + + Operand mask2 = context.AddIntrinsic(Intrinsic.X86Pand, opF, qMask); + mask2 = context.AddIntrinsic(Intrinsic.X86Cmpps, mask2, qMask, Const((int)CmpCondition.Equal)); + + qNaNMask = isQNaN == null || (bool)isQNaN ? context.AddIntrinsic(Intrinsic.X86Andps, mask2, mask1) : default; + sNaNMask = isQNaN == null || !(bool)isQNaN ? context.AddIntrinsic(Intrinsic.X86Andnps, mask2, mask1) : default; + } + else /* if ((op.Size & 1) == 1) */ + { + const int QBit = 51; + + Operand qMask = X86GetAllElements(context, 1L << QBit); + + Operand mask1 = context.AddIntrinsic(Intrinsic.X86Cmppd, opF, opF, Const((int)CmpCondition.UnorderedQ)); + + Operand mask2 = context.AddIntrinsic(Intrinsic.X86Pand, opF, qMask); + mask2 = context.AddIntrinsic(Intrinsic.X86Cmppd, mask2, qMask, Const((int)CmpCondition.Equal)); + + qNaNMask = isQNaN == null || (bool)isQNaN ? context.AddIntrinsic(Intrinsic.X86Andpd, mask2, mask1) : default; + sNaNMask = isQNaN == null || !(bool)isQNaN ? context.AddIntrinsic(Intrinsic.X86Andnpd, mask2, mask1) : default; + } + } + + public static Operand EmitSse41ProcessNaNsOpF( + ArmEmitterContext context, + Func2I emit, + bool scalar, + Operand n = default, + Operand m = default) + { + Operand nCopy = n == default ? context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rn)) : n; + Operand mCopy = m == default ? context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rm)) : m; + + EmitSse2VectorIsNaNOpF(context, nCopy, out Operand nQNaNMask, out Operand nSNaNMask); + EmitSse2VectorIsNaNOpF(context, mCopy, out _, out Operand mSNaNMask, isQNaN: false); + + int sizeF = ((IOpCodeSimd)context.CurrOp).Size & 1; + + if (sizeF == 0) + { + const int QBit = 22; + + Operand qMask = scalar ? X86GetScalar(context, 1 << QBit) : X86GetAllElements(context, 1 << QBit); + + Operand resNaNMask = context.AddIntrinsic(Intrinsic.X86Pandn, mSNaNMask, nQNaNMask); + resNaNMask = context.AddIntrinsic(Intrinsic.X86Por, resNaNMask, nSNaNMask); + + Operand resNaN = context.AddIntrinsic(Intrinsic.X86Blendvps, mCopy, nCopy, resNaNMask); + resNaN = context.AddIntrinsic(Intrinsic.X86Por, resNaN, qMask); + + Operand resMask = context.AddIntrinsic(Intrinsic.X86Cmpps, nCopy, mCopy, Const((int)CmpCondition.OrderedQ)); + + Operand res = context.AddIntrinsic(Intrinsic.X86Blendvps, resNaN, emit(nCopy, mCopy), resMask); + + if (n != default || m != default) + { + return res; + } + + if (scalar) + { + res = context.VectorZeroUpper96(res); + } + else if (((OpCodeSimdReg)context.CurrOp).RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rd), res); + + return default; + } + else /* if (sizeF == 1) */ + { + const int QBit = 51; + + Operand qMask = scalar ? X86GetScalar(context, 1L << QBit) : X86GetAllElements(context, 1L << QBit); + + Operand resNaNMask = context.AddIntrinsic(Intrinsic.X86Pandn, mSNaNMask, nQNaNMask); + resNaNMask = context.AddIntrinsic(Intrinsic.X86Por, resNaNMask, nSNaNMask); + + Operand resNaN = context.AddIntrinsic(Intrinsic.X86Blendvpd, mCopy, nCopy, resNaNMask); + resNaN = context.AddIntrinsic(Intrinsic.X86Por, resNaN, qMask); + + Operand resMask = context.AddIntrinsic(Intrinsic.X86Cmppd, nCopy, mCopy, Const((int)CmpCondition.OrderedQ)); + + Operand res = context.AddIntrinsic(Intrinsic.X86Blendvpd, resNaN, emit(nCopy, mCopy), resMask); + + if (n != default || m != default) + { + return res; + } + + if (scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rd), res); + + return default; + } + } + + private static Operand EmitSse2VectorMaxMinOpF(ArmEmitterContext context, Operand n, Operand m, bool isMax) + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + if ((op.Size & 1) == 0) + { + Operand mask = X86GetAllElements(context, -0f); + + Operand res = context.AddIntrinsic(isMax ? Intrinsic.X86Maxps : Intrinsic.X86Minps, n, m); + res = context.AddIntrinsic(Intrinsic.X86Andnps, mask, res); + + Operand resSign = context.AddIntrinsic(isMax ? Intrinsic.X86Pand : Intrinsic.X86Por, n, m); + resSign = context.AddIntrinsic(Intrinsic.X86Andps, mask, resSign); + + return context.AddIntrinsic(Intrinsic.X86Por, res, resSign); + } + else /* if ((op.Size & 1) == 1) */ + { + Operand mask = X86GetAllElements(context, -0d); + + Operand res = context.AddIntrinsic(isMax ? Intrinsic.X86Maxpd : Intrinsic.X86Minpd, n, m); + res = context.AddIntrinsic(Intrinsic.X86Andnpd, mask, res); + + Operand resSign = context.AddIntrinsic(isMax ? Intrinsic.X86Pand : Intrinsic.X86Por, n, m); + resSign = context.AddIntrinsic(Intrinsic.X86Andpd, mask, resSign); + + return context.AddIntrinsic(Intrinsic.X86Por, res, resSign); + } + } + + private static Operand EmitSse41MaxMinNumOpF( + ArmEmitterContext context, + bool isMaxNum, + bool scalar, + Operand n = default, + Operand m = default) + { + Operand nCopy = n == default ? context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rn)) : n; + Operand mCopy = m == default ? context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rm)) : m; + + EmitSse2VectorIsNaNOpF(context, nCopy, out Operand nQNaNMask, out _, isQNaN: true); + EmitSse2VectorIsNaNOpF(context, mCopy, out Operand mQNaNMask, out _, isQNaN: true); + + int sizeF = ((IOpCodeSimd)context.CurrOp).Size & 1; + + if (sizeF == 0) + { + Operand negInfMask = scalar + ? X86GetScalar (context, isMaxNum ? float.NegativeInfinity : float.PositiveInfinity) + : X86GetAllElements(context, isMaxNum ? float.NegativeInfinity : float.PositiveInfinity); + + Operand nMask = context.AddIntrinsic(Intrinsic.X86Andnps, mQNaNMask, nQNaNMask); + Operand mMask = context.AddIntrinsic(Intrinsic.X86Andnps, nQNaNMask, mQNaNMask); + + nCopy = context.AddIntrinsic(Intrinsic.X86Blendvps, nCopy, negInfMask, nMask); + mCopy = context.AddIntrinsic(Intrinsic.X86Blendvps, mCopy, negInfMask, mMask); + + Operand res = EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum); + }, scalar: scalar, nCopy, mCopy); + + if (n != default || m != default) + { + return res; + } + + if (scalar) + { + res = context.VectorZeroUpper96(res); + } + else if (((OpCodeSimdReg)context.CurrOp).RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rd), res); + + return default; + } + else /* if (sizeF == 1) */ + { + Operand negInfMask = scalar + ? X86GetScalar (context, isMaxNum ? double.NegativeInfinity : double.PositiveInfinity) + : X86GetAllElements(context, isMaxNum ? double.NegativeInfinity : double.PositiveInfinity); + + Operand nMask = context.AddIntrinsic(Intrinsic.X86Andnpd, mQNaNMask, nQNaNMask); + Operand mMask = context.AddIntrinsic(Intrinsic.X86Andnpd, nQNaNMask, mQNaNMask); + + nCopy = context.AddIntrinsic(Intrinsic.X86Blendvpd, nCopy, negInfMask, nMask); + mCopy = context.AddIntrinsic(Intrinsic.X86Blendvpd, mCopy, negInfMask, mMask); + + Operand res = EmitSse41ProcessNaNsOpF(context, (op1, op2) => + { + return EmitSse2VectorMaxMinOpF(context, op1, op2, isMax: isMaxNum); + }, scalar: scalar, nCopy, mCopy); + + if (n != default || m != default) + { + return res; + } + + if (scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(((OpCodeSimdReg)context.CurrOp).Rd), res); + + return default; + } + } + + private enum AddSub + { + None, + Add, + Subtract + } + + private static void EmitSse41VectorMul_AddSub(ArmEmitterContext context, AddSub addSub) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res; + + if (op.Size == 0) + { + Operand ns8 = context.AddIntrinsic(Intrinsic.X86Psrlw, n, Const(8)); + Operand ms8 = context.AddIntrinsic(Intrinsic.X86Psrlw, m, Const(8)); + + res = context.AddIntrinsic(Intrinsic.X86Pmullw, ns8, ms8); + + res = context.AddIntrinsic(Intrinsic.X86Psllw, res, Const(8)); + + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pmullw, n, m); + + Operand mask = X86GetAllElements(context, 0x00FF00FF); + + res = context.AddIntrinsic(Intrinsic.X86Pblendvb, res, res2, mask); + } + else if (op.Size == 1) + { + res = context.AddIntrinsic(Intrinsic.X86Pmullw, n, m); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Pmulld, n, m); + } + + Operand d = GetVec(op.Rd); + + if (addSub == AddSub.Add) + { + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, d, res); + } + else if (addSub == AddSub.Subtract) + { + Intrinsic subInst = X86PsubInstruction[op.Size]; + + res = context.AddIntrinsic(subInst, d, res); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + + private static void EmitSse41VectorSabdOp( + ArmEmitterContext context, + OpCodeSimdReg op, + Operand n, + Operand m, + bool isLong) + { + int size = isLong ? op.Size + 1 : op.Size; + + Intrinsic cmpgtInst = X86PcmpgtInstruction[size]; + + Operand cmpMask = context.AddIntrinsic(cmpgtInst, n, m); + + Intrinsic subInst = X86PsubInstruction[size]; + + Operand res = context.AddIntrinsic(subInst, n, m); + + res = context.AddIntrinsic(Intrinsic.X86Pand, cmpMask, res); + + Operand res2 = context.AddIntrinsic(subInst, m, n); + + res2 = context.AddIntrinsic(Intrinsic.X86Pandn, cmpMask, res2); + + res = context.AddIntrinsic(Intrinsic.X86Por, res, res2); + + if (!isLong && op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitSse41VectorUabdOp( + ArmEmitterContext context, + OpCodeSimdReg op, + Operand n, + Operand m, + bool isLong) + { + int size = isLong ? op.Size + 1 : op.Size; + + Intrinsic maxInst = X86PmaxuInstruction[size]; + + Operand max = context.AddIntrinsic(maxInst, m, n); + + Intrinsic cmpeqInst = X86PcmpeqInstruction[size]; + + Operand cmpMask = context.AddIntrinsic(cmpeqInst, max, m); + + Operand onesMask = X86GetAllElements(context, -1L); + + cmpMask = context.AddIntrinsic(Intrinsic.X86Pandn, cmpMask, onesMask); + + Intrinsic subInst = X86PsubInstruction[size]; + + Operand res = context.AddIntrinsic(subInst, n, m); + Operand res2 = context.AddIntrinsic(subInst, m, n); + + res = context.AddIntrinsic(Intrinsic.X86Pand, cmpMask, res); + res2 = context.AddIntrinsic(Intrinsic.X86Pandn, cmpMask, res2); + + res = context.AddIntrinsic(Intrinsic.X86Por, res, res2); + + if (!isLong && op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static Operand EmitSse2Sll_128(ArmEmitterContext context, Operand op, int shift) + { + // The upper part of op is assumed to be zero. + Debug.Assert(shift >= 0 && shift < 64); + + if (shift == 0) + { + return op; + } + + Operand high = context.AddIntrinsic(Intrinsic.X86Pslldq, op, Const(8)); + high = context.AddIntrinsic(Intrinsic.X86Psrlq, high, Const(64 - shift)); + + Operand low = context.AddIntrinsic(Intrinsic.X86Psllq, op, Const(shift)); + + return context.AddIntrinsic(Intrinsic.X86Por, high, low); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs b/src/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs new file mode 100644 index 00000000..a9994e41 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdArithmetic32.cs @@ -0,0 +1,1703 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; + +using static ARMeilleure.Instructions.InstEmitFlowHelper; +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper32; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void Vabd_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitVectorBinaryOpI32(context, (op1, op2) => EmitAbs(context, context.Subtract(op1, op2)), !op.U); + } + + public static void Vabdl_I(ArmEmitterContext context) + { + OpCode32SimdRegLong op = (OpCode32SimdRegLong)context.CurrOp; + + EmitVectorBinaryLongOpI32(context, (op1, op2) => EmitAbs(context, context.Subtract(op1, op2)), !op.U); + } + + public static void Vabs_S(ArmEmitterContext context) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FabsS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarUnaryOpSimd32(context, (m) => + { + return EmitFloatAbs(context, m, (op.Size & 1) == 0, false); + }); + } + else + { + EmitScalarUnaryOpF32(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Abs), op1)); + } + } + + public static void Vabs_V(ArmEmitterContext context) + { + OpCode32SimdCmpZ op = (OpCode32SimdCmpZ)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FabsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorUnaryOpSimd32(context, (m) => + { + return EmitFloatAbs(context, m, (op.Size & 1) == 0, true); + }); + } + else + { + EmitVectorUnaryOpF32(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Abs), op1)); + } + } + else + { + EmitVectorUnaryOpSx32(context, (op1) => EmitAbs(context, op1)); + } + } + + private static Operand EmitAbs(ArmEmitterContext context, Operand value) + { + Operand isPositive = context.ICompareGreaterOrEqual(value, Const(value.Type, 0)); + + return context.ConditionalSelect(isPositive, value, context.Negate(value)); + } + + public static void Vadd_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarBinaryOpF32(context, Intrinsic.Arm64FaddS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF32(context, Intrinsic.X86Addss, Intrinsic.X86Addsd); + } + else if (Optimizations.FastFP) + { + EmitScalarBinaryOpF32(context, (op1, op2) => context.Add(op1, op2)); + } + else + { + EmitScalarBinaryOpF32(context, (op1, op2) => EmitSoftFloatCall(context, nameof(SoftFloat32.FPAdd), op1, op2)); + } + } + + public static void Vadd_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FaddV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF32(context, Intrinsic.X86Addps, Intrinsic.X86Addpd); + } + else if (Optimizations.FastFP) + { + EmitVectorBinaryOpF32(context, (op1, op2) => context.Add(op1, op2)); + } + else + { + EmitVectorBinaryOpF32(context, (op1, op2) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPAddFpscr), op1, op2)); + } + } + + public static void Vadd_I(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + EmitVectorBinaryOpSimd32(context, (op1, op2) => context.AddIntrinsic(X86PaddInstruction[op.Size], op1, op2)); + } + else + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.Add(op1, op2)); + } + } + + public static void Vaddl_I(ArmEmitterContext context) + { + OpCode32SimdRegLong op = (OpCode32SimdRegLong)context.CurrOp; + + EmitVectorBinaryLongOpI32(context, (op1, op2) => context.Add(op1, op2), !op.U); + } + + public static void Vaddw_I(ArmEmitterContext context) + { + OpCode32SimdRegWide op = (OpCode32SimdRegWide)context.CurrOp; + + EmitVectorBinaryWideOpI32(context, (op1, op2) => context.Add(op1, op2), !op.U); + } + + public static void Vcnt(ArmEmitterContext context) + { + OpCode32SimdCmpZ op = (OpCode32SimdCmpZ)context.CurrOp; + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount(); + + for (int index = 0; index < elems; index++) + { + Operand de; + Operand me = EmitVectorExtractZx32(context, op.Qm, op.Im + index, op.Size); + + if (Optimizations.UsePopCnt) + { + de = context.AddIntrinsicInt(Intrinsic.X86Popcnt, me); + } + else + { + de = EmitCountSetBits8(context, me); + } + + res = EmitVectorInsert(context, res, de, op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Vdup(ArmEmitterContext context) + { + OpCode32SimdDupGP op = (OpCode32SimdDupGP)context.CurrOp; + + Operand insert = GetIntA32(context, op.Rt); + + // Zero extend into an I64, then replicate. Saves the most time over elementwise inserts. + insert = op.Size switch + { + 2 => context.Multiply(context.ZeroExtend32(OperandType.I64, insert), Const(0x0000000100000001u)), + 1 => context.Multiply(context.ZeroExtend16(OperandType.I64, insert), Const(0x0001000100010001u)), + 0 => context.Multiply(context.ZeroExtend8(OperandType.I64, insert), Const(0x0101010101010101u)), + _ => throw new InvalidOperationException($"Invalid Vdup size \"{op.Size}\".") + }; + + InsertScalar(context, op.Vd, insert); + if (op.Q) + { + InsertScalar(context, op.Vd + 1, insert); + } + } + + public static void Vdup_1(ArmEmitterContext context) + { + OpCode32SimdDupElem op = (OpCode32SimdDupElem)context.CurrOp; + + Operand insert = EmitVectorExtractZx32(context, op.Vm >> 1, ((op.Vm & 1) << (3 - op.Size)) + op.Index, op.Size); + + // Zero extend into an I64, then replicate. Saves the most time over elementwise inserts. + insert = op.Size switch + { + 2 => context.Multiply(context.ZeroExtend32(OperandType.I64, insert), Const(0x0000000100000001u)), + 1 => context.Multiply(context.ZeroExtend16(OperandType.I64, insert), Const(0x0001000100010001u)), + 0 => context.Multiply(context.ZeroExtend8(OperandType.I64, insert), Const(0x0101010101010101u)), + _ => throw new InvalidOperationException($"Invalid Vdup size \"{op.Size}\".") + }; + + InsertScalar(context, op.Vd, insert); + if (op.Q) + { + InsertScalar(context, op.Vd | 1, insert); + } + } + + private static (long, long) MaskHelperByteSequence(int start, int length, int startByte) + { + int end = start + length; + int b = startByte; + long result = 0; + long result2 = 0; + for (int i = 0; i < 8; i++) + { + result |= (long)((i >= end || i < start) ? 0x80 : b++) << (i * 8); + } + for (int i = 8; i < 16; i++) + { + result2 |= (long)((i >= end || i < start) ? 0x80 : b++) << ((i - 8) * 8); + } + return (result2, result); + } + + public static void Vext(ArmEmitterContext context) + { + OpCode32SimdExt op = (OpCode32SimdExt)context.CurrOp; + int elems = op.GetBytesCount(); + int byteOff = op.Immediate; + + if (Optimizations.UseSsse3) + { + EmitVectorBinaryOpSimd32(context, (n, m) => + { + // Writing low to high of d: start into n, overlap into m. + // Then rotate n down by , m up by (elems)-imm. + // Then OR them together for the result. + + (long nMaskHigh, long nMaskLow) = MaskHelperByteSequence(0, elems - byteOff, byteOff); + (long mMaskHigh, long mMaskLow) = MaskHelperByteSequence(elems - byteOff, byteOff, 0); + Operand nMask, mMask; + if (!op.Q) + { + // Do the same operation to the bytes in the top doubleword too, as our target could be in either. + nMaskHigh = nMaskLow + 0x0808080808080808L; + mMaskHigh = mMaskLow + 0x0808080808080808L; + } + nMask = X86GetElements(context, nMaskHigh, nMaskLow); + mMask = X86GetElements(context, mMaskHigh, mMaskLow); + Operand nPart = context.AddIntrinsic(Intrinsic.X86Pshufb, n, nMask); + Operand mPart = context.AddIntrinsic(Intrinsic.X86Pshufb, m, mMask); + + return context.AddIntrinsic(Intrinsic.X86Por, nPart, mPart); + }); + } + else + { + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < elems; index++) + { + Operand extract; + + if (byteOff >= elems) + { + extract = EmitVectorExtractZx32(context, op.Qm, op.Im + (byteOff - elems), op.Size); + } + else + { + extract = EmitVectorExtractZx32(context, op.Qn, op.In + byteOff, op.Size); + } + byteOff++; + + res = EmitVectorInsert(context, res, extract, op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + } + + public static void Vfma_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FmaddS); + } + else if (Optimizations.FastFP && Optimizations.UseFma) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Vfmadd231ss, Intrinsic.X86Vfmadd231sd); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Addss, Intrinsic.X86Addsd); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulAdd), op1, op2, op3); + }); + } + } + + public static void Vfma_V(ArmEmitterContext context) // Fused. + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorTernaryOpF32(context, Intrinsic.Arm64FmlaV); + } + else if (Optimizations.FastFP && Optimizations.UseFma) + { + EmitVectorTernaryOpF32(context, Intrinsic.X86Vfmadd231ps); + } + else + { + EmitVectorTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulAddFpscr), op1, op2, op3); + }); + } + } + + public static void Vfms_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FmsubS); + } + else if (Optimizations.FastFP && Optimizations.UseFma) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Vfnmadd231ss, Intrinsic.X86Vfnmadd231sd); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Subss, Intrinsic.X86Subsd); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMulSub), op1, op2, op3); + }); + } + } + + public static void Vfms_V(ArmEmitterContext context) // Fused. + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorTernaryOpF32(context, Intrinsic.Arm64FmlsV); + } + else if (Optimizations.FastFP && Optimizations.UseFma) + { + EmitVectorTernaryOpF32(context, Intrinsic.X86Vfnmadd231ps); + } + else + { + EmitVectorTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulSubFpscr), op1, op2, op3); + }); + } + } + + public static void Vfnma_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FnmaddS); + } + else if (Optimizations.FastFP && Optimizations.UseFma) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Vfnmsub231ss, Intrinsic.X86Vfnmsub231sd); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Subss, Intrinsic.X86Subsd, isNegD: true); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPNegMulAdd), op1, op2, op3); + }); + } + } + + public static void Vfnms_S(ArmEmitterContext context) // Fused. + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FnmsubS); + } + else if (Optimizations.FastFP && Optimizations.UseFma) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Vfmsub231ss, Intrinsic.X86Vfmsub231sd); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Addss, Intrinsic.X86Addsd, isNegD: true); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPNegMulSub), op1, op2, op3); + }); + } + } + + public static void Vhadd(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (op.U) + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.ShiftRightUI(context.Add(op1, op2), Const(1))); + } + else + { + EmitVectorBinaryOpSx32(context, (op1, op2) => context.ShiftRightSI(context.Add(op1, op2), Const(1))); + } + } + + public static void Vmov_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarUnaryOpF32(context, 0, 0); + } + else + { + EmitScalarUnaryOpF32(context, (op1) => op1); + } + } + + public static void Vmovn(ArmEmitterContext context) + { + EmitVectorUnaryNarrowOp32(context, (op1) => op1); + } + + public static void Vneg_S(ArmEmitterContext context) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FnegS); + } + else if (Optimizations.UseSse2) + { + EmitScalarUnaryOpSimd32(context, (m) => + { + if ((op.Size & 1) == 0) + { + Operand mask = X86GetScalar(context, -0f); + return context.AddIntrinsic(Intrinsic.X86Xorps, mask, m); + } + else + { + Operand mask = X86GetScalar(context, -0d); + return context.AddIntrinsic(Intrinsic.X86Xorpd, mask, m); + } + }); + } + else + { + EmitScalarUnaryOpF32(context, (op1) => context.Negate(op1)); + } + } + + public static void Vnmul_S(ArmEmitterContext context) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarBinaryOpF32(context, Intrinsic.Arm64FnmulS); + } + else if (Optimizations.UseSse2) + { + EmitScalarBinaryOpSimd32(context, (n, m) => + { + if ((op.Size & 1) == 0) + { + Operand res = context.AddIntrinsic(Intrinsic.X86Mulss, n, m); + Operand mask = X86GetScalar(context, -0f); + return context.AddIntrinsic(Intrinsic.X86Xorps, mask, res); + } + else + { + Operand res = context.AddIntrinsic(Intrinsic.X86Mulsd, n, m); + Operand mask = X86GetScalar(context, -0d); + return context.AddIntrinsic(Intrinsic.X86Xorpd, mask, res); + } + }); + } + else + { + EmitScalarBinaryOpF32(context, (op1, op2) => context.Negate(context.Multiply(op1, op2))); + } + } + + public static void Vnmla_S(ArmEmitterContext context) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FnmaddS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Subss, Intrinsic.X86Subsd, isNegD: true); + } + else if (Optimizations.FastFP) + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return context.Subtract(context.Negate(op1), context.Multiply(op2, op3)); + }); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op2, op3); + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), context.Negate(op1), res); + }); + } + } + + public static void Vnmls_S(ArmEmitterContext context) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FnmsubS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Addss, Intrinsic.X86Addsd, isNegD: true); + } + else if (Optimizations.FastFP) + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return context.Add(context.Negate(op1), context.Multiply(op2, op3)); + }); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op2, op3); + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPAdd), context.Negate(op1), res); + }); + } + } + + public static void Vneg_V(ArmEmitterContext context) + { + OpCode32SimdCmpZ op = (OpCode32SimdCmpZ)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FnegV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorUnaryOpSimd32(context, (m) => + { + if ((op.Size & 1) == 0) + { + Operand mask = X86GetAllElements(context, -0f); + return context.AddIntrinsic(Intrinsic.X86Xorps, mask, m); + } + else + { + Operand mask = X86GetAllElements(context, -0d); + return context.AddIntrinsic(Intrinsic.X86Xorpd, mask, m); + } + }); + } + else + { + EmitVectorUnaryOpF32(context, (op1) => context.Negate(op1)); + } + } + else + { + EmitVectorUnaryOpSx32(context, (op1) => context.Negate(op1)); + } + } + + public static void Vdiv_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarBinaryOpF32(context, Intrinsic.Arm64FdivS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF32(context, Intrinsic.X86Divss, Intrinsic.X86Divsd); + } + else if (Optimizations.FastFP) + { + EmitScalarBinaryOpF32(context, (op1, op2) => context.Divide(op1, op2)); + } + else + { + EmitScalarBinaryOpF32(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPDiv), op1, op2); + }); + } + } + + public static void Vmaxnm_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarBinaryOpF32(context, Intrinsic.Arm64FmaxnmS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF32(context, true, true); + } + else + { + EmitScalarBinaryOpF32(context, (op1, op2) => EmitSoftFloatCall(context, nameof(SoftFloat32.FPMaxNum), op1, op2)); + } + } + + public static void Vmaxnm_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FmaxnmV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF32(context, true, false); + } + else + { + EmitVectorBinaryOpSx32(context, (op1, op2) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMaxNumFpscr), op1, op2)); + } + } + + public static void Vminnm_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarBinaryOpF32(context, Intrinsic.Arm64FminnmS); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF32(context, false, true); + } + else + { + EmitScalarBinaryOpF32(context, (op1, op2) => EmitSoftFloatCall(context, nameof(SoftFloat32.FPMinNum), op1, op2)); + } + } + + public static void Vminnm_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FminnmV); + } + else if (Optimizations.FastFP && Optimizations.UseSse41) + { + EmitSse41MaxMinNumOpF32(context, false, false); + } + else + { + EmitVectorBinaryOpSx32(context, (op1, op2) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMinNumFpscr), op1, op2)); + } + } + + public static void Vmax_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FmaxV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF32(context, Intrinsic.X86Maxps, Intrinsic.X86Maxpd); + } + else + { + EmitVectorBinaryOpF32(context, (op1, op2) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMaxFpscr), op1, op2); + }); + } + } + + public static void Vmax_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (op.U) + { + if (Optimizations.UseSse2) + { + EmitVectorBinaryOpSimd32(context, (op1, op2) => context.AddIntrinsic(X86PmaxuInstruction[op.Size], op1, op2)); + } + else + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.ConditionalSelect(context.ICompareGreaterUI(op1, op2), op1, op2)); + } + } + else + { + if (Optimizations.UseSse2) + { + EmitVectorBinaryOpSimd32(context, (op1, op2) => context.AddIntrinsic(X86PmaxsInstruction[op.Size], op1, op2)); + } + else + { + EmitVectorBinaryOpSx32(context, (op1, op2) => context.ConditionalSelect(context.ICompareGreater(op1, op2), op1, op2)); + } + } + } + + public static void Vmin_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FminV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF32(context, Intrinsic.X86Minps, Intrinsic.X86Minpd); + } + else + { + EmitVectorBinaryOpF32(context, (op1, op2) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMinFpscr), op1, op2); + }); + } + } + + public static void Vmin_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (op.U) + { + if (Optimizations.UseSse2) + { + EmitVectorBinaryOpSimd32(context, (op1, op2) => context.AddIntrinsic(X86PminuInstruction[op.Size], op1, op2)); + } + else + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.ConditionalSelect(context.ICompareLessUI(op1, op2), op1, op2)); + } + } + else + { + if (Optimizations.UseSse2) + { + EmitVectorBinaryOpSimd32(context, (op1, op2) => context.AddIntrinsic(X86PminsInstruction[op.Size], op1, op2)); + } + else + { + EmitVectorBinaryOpSx32(context, (op1, op2) => context.ConditionalSelect(context.ICompareLess(op1, op2), op1, op2)); + } + } + } + + public static void Vmla_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FmaddS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Addss, Intrinsic.X86Addsd); + } + else if (Optimizations.FastFP) + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return context.Add(op1, context.Multiply(op2, op3)); + }); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op2, op3); + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPAdd), op1, res); + }); + } + } + + public static void Vmla_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorTernaryOpF32(context, Intrinsic.Arm64FmlaV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorTernaryOpF32(context, Intrinsic.X86Mulps, Intrinsic.X86Mulpd, Intrinsic.X86Addps, Intrinsic.X86Addpd); + } + else if (Optimizations.FastFP) + { + EmitVectorTernaryOpF32(context, (op1, op2, op3) => context.Add(op1, context.Multiply(op2, op3))); + } + else + { + EmitVectorTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulAddFpscr), op1, op2, op3); + }); + } + } + + public static void Vmla_I(ArmEmitterContext context) + { + EmitVectorTernaryOpZx32(context, (op1, op2, op3) => context.Add(op1, context.Multiply(op2, op3))); + } + + public static void Vmla_1(ArmEmitterContext context) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorsByScalarOpF32(context, Intrinsic.X86Mulps, Intrinsic.X86Mulpd, Intrinsic.X86Addps, Intrinsic.X86Addpd); + } + else if (Optimizations.FastFP) + { + EmitVectorsByScalarOpF32(context, (op1, op2, op3) => context.Add(op1, context.Multiply(op2, op3))); + } + else + { + EmitVectorsByScalarOpF32(context, (op1, op2, op3) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulAddFpscr), op1, op2, op3)); + } + } + else + { + EmitVectorsByScalarOpI32(context, (op1, op2, op3) => context.Add(op1, context.Multiply(op2, op3)), false); + } + } + + public static void Vmlal_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitVectorTernaryLongOpI32(context, (d, n, m) => context.Add(d, context.Multiply(n, m)), !op.U); + } + + public static void Vmls_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarTernaryOpF32(context, Intrinsic.Arm64FmlsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarTernaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd, Intrinsic.X86Subss, Intrinsic.X86Subsd); + } + else if (Optimizations.FastFP) + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + return context.Subtract(op1, context.Multiply(op2, op3)); + }); + } + else + { + EmitScalarTernaryOpF32(context, (op1, op2, op3) => + { + Operand res = EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op2, op3); + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPSub), op1, res); + }); + } + } + + public static void Vmls_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorTernaryOpF32(context, Intrinsic.Arm64FmlsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorTernaryOpF32(context, Intrinsic.X86Mulps, Intrinsic.X86Mulpd, Intrinsic.X86Subps, Intrinsic.X86Subpd); + } + else if (Optimizations.FastFP) + { + EmitVectorTernaryOpF32(context, (op1, op2, op3) => context.Subtract(op1, context.Multiply(op2, op3))); + } + else + { + EmitVectorTernaryOpF32(context, (op1, op2, op3) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulSubFpscr), op1, op2, op3); + }); + } + } + + public static void Vmls_I(ArmEmitterContext context) + { + EmitVectorTernaryOpZx32(context, (op1, op2, op3) => context.Subtract(op1, context.Multiply(op2, op3))); + } + + public static void Vmls_1(ArmEmitterContext context) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorsByScalarOpF32(context, Intrinsic.X86Mulps, Intrinsic.X86Mulpd, Intrinsic.X86Subps, Intrinsic.X86Subpd); + } + else if (Optimizations.FastFP) + { + EmitVectorsByScalarOpF32(context, (op1, op2, op3) => context.Subtract(op1, context.Multiply(op2, op3))); + } + else + { + EmitVectorsByScalarOpF32(context, (op1, op2, op3) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulSubFpscr), op1, op2, op3)); + } + } + else + { + EmitVectorsByScalarOpI32(context, (op1, op2, op3) => context.Subtract(op1, context.Multiply(op2, op3)), false); + } + } + + public static void Vmlsl_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitVectorTernaryLongOpI32(context, (opD, op1, op2) => context.Subtract(opD, context.Multiply(op1, op2)), !op.U); + } + + public static void Vmul_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarBinaryOpF32(context, Intrinsic.Arm64FmulS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF32(context, Intrinsic.X86Mulss, Intrinsic.X86Mulsd); + } + else if (Optimizations.FastFP) + { + EmitScalarBinaryOpF32(context, (op1, op2) => context.Multiply(op1, op2)); + } + else + { + EmitScalarBinaryOpF32(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPMul), op1, op2); + }); + } + } + + public static void Vmul_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FmulV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF32(context, Intrinsic.X86Mulps, Intrinsic.X86Mulpd); + } + else if (Optimizations.FastFP) + { + EmitVectorBinaryOpF32(context, (op1, op2) => context.Multiply(op1, op2)); + } + else + { + EmitVectorBinaryOpF32(context, (op1, op2) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulFpscr), op1, op2); + }); + } + } + + public static void Vmul_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (op.U) // This instruction is always signed, U indicates polynomial mode. + { + EmitVectorBinaryOpZx32(context, (op1, op2) => EmitPolynomialMultiply(context, op1, op2, 8 << op.Size)); + } + else + { + EmitVectorBinaryOpSx32(context, (op1, op2) => context.Multiply(op1, op2)); + } + } + + public static void Vmul_1(ArmEmitterContext context) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorByScalarOpF32(context, Intrinsic.X86Mulps, Intrinsic.X86Mulpd); + } + else if (Optimizations.FastFP) + { + EmitVectorByScalarOpF32(context, (op1, op2) => context.Multiply(op1, op2)); + } + else + { + EmitVectorByScalarOpF32(context, (op1, op2) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMulFpscr), op1, op2)); + } + } + else + { + EmitVectorByScalarOpI32(context, (op1, op2) => context.Multiply(op1, op2), false); + } + } + + public static void Vmull_1(ArmEmitterContext context) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + EmitVectorByScalarLongOpI32(context, (op1, op2) => context.Multiply(op1, op2), !op.U); + } + + public static void Vmull_I(ArmEmitterContext context) + { + OpCode32SimdRegLong op = (OpCode32SimdRegLong)context.CurrOp; + + if (op.Polynomial) + { + if (op.Size == 0) // P8 + { + EmitVectorBinaryLongOpI32(context, (op1, op2) => EmitPolynomialMultiply(context, op1, op2, 8 << op.Size), false); + } + else /* if (op.Size == 2) // P64 */ + { + Operand ne = context.VectorExtract(OperandType.I64, GetVec(op.Qn), op.Vn & 1); + Operand me = context.VectorExtract(OperandType.I64, GetVec(op.Qm), op.Vm & 1); + + Operand res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.PolynomialMult64_128)), ne, me); + + context.Copy(GetVecA32(op.Qd), res); + } + } + else + { + EmitVectorBinaryLongOpI32(context, (op1, op2) => context.Multiply(op1, op2), !op.U); + } + } + + public static void Vpadd_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorPairwiseOpF32(context, Intrinsic.Arm64FaddpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2VectorPairwiseOpF32(context, Intrinsic.X86Addps); + } + else + { + EmitVectorPairwiseOpF32(context, (op1, op2) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPAddFpscr), op1, op2)); + } + } + + public static void Vpadd_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp32(context, X86PaddInstruction); + } + else + { + EmitVectorPairwiseOpI32(context, (op1, op2) => context.Add(op1, op2), !op.U); + } + } + + public static void Vpaddl(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + EmitVectorPairwiseLongOpI32(context, (op1, op2) => context.Add(op1, op2), (op.Opc & 1) == 0); + } + + public static void Vpmax_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorPairwiseOpF32(context, Intrinsic.Arm64FmaxpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2VectorPairwiseOpF32(context, Intrinsic.X86Maxps); + } + else + { + EmitVectorPairwiseOpF32(context, (op1, op2) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat64.FPMaxFpscr), op1, op2)); + } + } + + public static void Vpmax_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp32(context, op.U ? X86PmaxuInstruction : X86PmaxsInstruction); + } + else + { + EmitVectorPairwiseOpI32(context, (op1, op2) => + { + Operand greater = op.U ? context.ICompareGreaterUI(op1, op2) : context.ICompareGreater(op1, op2); + return context.ConditionalSelect(greater, op1, op2); + }, !op.U); + } + } + + public static void Vpmin_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorPairwiseOpF32(context, Intrinsic.Arm64FminpV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2VectorPairwiseOpF32(context, Intrinsic.X86Minps); + } + else + { + EmitVectorPairwiseOpF32(context, (op1, op2) => EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPMinFpscr), op1, op2)); + } + } + + public static void Vpmin_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (Optimizations.UseSsse3) + { + EmitSsse3VectorPairwiseOp32(context, op.U ? X86PminuInstruction : X86PminsInstruction); + } + else + { + EmitVectorPairwiseOpI32(context, (op1, op2) => + { + Operand greater = op.U ? context.ICompareLessUI(op1, op2) : context.ICompareLess(op1, op2); + return context.ConditionalSelect(greater, op1, op2); + }, !op.U); + } + } + + public static void Vqadd(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitSaturatingAddSubBinaryOp(context, add: true, !op.U); + } + + public static void Vqdmulh(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + int eSize = 8 << op.Size; + + EmitVectorBinaryOpI32(context, (op1, op2) => + { + if (op.Size == 2) + { + op1 = context.SignExtend32(OperandType.I64, op1); + op2 = context.SignExtend32(OperandType.I64, op2); + } + + Operand res = context.Multiply(op1, op2); + res = context.ShiftRightSI(res, Const(eSize - 1)); + res = EmitSatQ(context, res, eSize, signedSrc: true, signedDst: true); + + if (op.Size == 2) + { + res = context.ConvertI64ToI32(res); + } + + return res; + }, signed: true); + } + + public static void Vqmovn(ArmEmitterContext context) + { + OpCode32SimdMovn op = (OpCode32SimdMovn)context.CurrOp; + + bool signed = !op.Q; + + EmitVectorUnaryNarrowOp32(context, (op1) => EmitSatQ(context, op1, 8 << op.Size, signed, signed), signed); + } + + public static void Vqmovun(ArmEmitterContext context) + { + OpCode32SimdMovn op = (OpCode32SimdMovn)context.CurrOp; + + EmitVectorUnaryNarrowOp32(context, (op1) => EmitSatQ(context, op1, 8 << op.Size, signedSrc: true, signedDst: false), signed: true); + } + + public static void Vqsub(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitSaturatingAddSubBinaryOp(context, add: false, !op.U); + } + + public static void Vrev(ArmEmitterContext context) + { + OpCode32SimdRev op = (OpCode32SimdRev)context.CurrOp; + + if (Optimizations.UseSsse3) + { + EmitVectorUnaryOpSimd32(context, (op1) => + { + Operand mask; + switch (op.Size) + { + case 3: + // Rev64 + switch (op.Opc) + { + case 0: + mask = X86GetElements(context, 0x08090a0b0c0d0e0fL, 0x0001020304050607L); + return context.AddIntrinsic(Intrinsic.X86Pshufb, op1, mask); + case 1: + mask = X86GetElements(context, 0x09080b0a0d0c0f0eL, 0x0100030205040706L); + return context.AddIntrinsic(Intrinsic.X86Pshufb, op1, mask); + case 2: + return context.AddIntrinsic(Intrinsic.X86Shufps, op1, op1, Const(1 | (0 << 2) | (3 << 4) | (2 << 6))); + } + break; + case 2: + // Rev32 + switch (op.Opc) + { + case 0: + mask = X86GetElements(context, 0x0c0d0e0f_08090a0bL, 0x04050607_00010203L); + return context.AddIntrinsic(Intrinsic.X86Pshufb, op1, mask); + case 1: + mask = X86GetElements(context, 0x0d0c0f0e_09080b0aL, 0x05040706_01000302L); + return context.AddIntrinsic(Intrinsic.X86Pshufb, op1, mask); + } + break; + case 1: + // Rev16 + mask = X86GetElements(context, 0x0e0f_0c0d_0a0b_0809L, 0x_0607_0405_0203_0001L); + return context.AddIntrinsic(Intrinsic.X86Pshufb, op1, mask); + } + + throw new InvalidOperationException("Invalid VREV Opcode + Size combo."); // Should be unreachable. + }); + } + else + { + EmitVectorUnaryOpZx32(context, (op1) => + { + switch (op.Opc) + { + case 0: + switch (op.Size) // Swap bytes. + { + case 1: + return InstEmitAluHelper.EmitReverseBytes16_32Op(context, op1); + case 2: + case 3: + return context.ByteSwap(op1); + } + break; + case 1: + switch (op.Size) + { + case 2: + return context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op1, Const(0xffff0000)), Const(16)), + context.ShiftLeft(context.BitwiseAnd(op1, Const(0x0000ffff)), Const(16))); + case 3: + return context.BitwiseOr( + context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op1, Const(0xffff000000000000ul)), Const(48)), + context.ShiftLeft(context.BitwiseAnd(op1, Const(0x000000000000fffful)), Const(48))), + context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op1, Const(0x0000ffff00000000ul)), Const(16)), + context.ShiftLeft(context.BitwiseAnd(op1, Const(0x00000000ffff0000ul)), Const(16)))); + } + break; + case 2: + // Swap upper and lower halves. + return context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op1, Const(0xffffffff00000000ul)), Const(32)), + context.ShiftLeft(context.BitwiseAnd(op1, Const(0x00000000fffffffful)), Const(32))); + } + + throw new InvalidOperationException("Invalid VREV Opcode + Size combo."); // Should be unreachable. + }); + } + } + + public static void Vrecpe(ArmEmitterContext context) + { + OpCode32SimdSqrte op = (OpCode32SimdSqrte)context.CurrOp; + + if (op.F) + { + int sizeF = op.Size & 1; + + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FrecpeV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2 && sizeF == 0) + { + EmitVectorUnaryOpF32(context, Intrinsic.X86Rcpps, 0); + } + else + { + EmitVectorUnaryOpF32(context, (op1) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPRecipEstimateFpscr), op1); + }); + } + } + else + { + throw new NotImplementedException("Integer Vrecpe not currently implemented."); + } + } + + public static void Vrecps(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FrecpsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + bool single = (op.Size & 1) == 0; + + // (2 - (n*m)) + EmitVectorBinaryOpSimd32(context, (n, m) => + { + if (single) + { + Operand maskTwo = X86GetAllElements(context, 2f); + + Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m); + + return context.AddIntrinsic(Intrinsic.X86Subps, maskTwo, res); + } + else + { + Operand maskTwo = X86GetAllElements(context, 2d); + + Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m); + + return context.AddIntrinsic(Intrinsic.X86Subpd, maskTwo, res); + } + }); + } + else + { + EmitVectorBinaryOpF32(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRecipStep), op1, op2); + }); + } + } + + public static void Vrhadd(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitVectorBinaryOpI32(context, (op1, op2) => + { + if (op.Size == 2) + { + op1 = context.ZeroExtend32(OperandType.I64, op1); + op2 = context.ZeroExtend32(OperandType.I64, op2); + } + + Operand res = context.Add(context.Add(op1, op2), Const(op1.Type, 1L)); + res = context.ShiftRightUI(res, Const(1)); + + if (op.Size == 2) + { + res = context.ConvertI64ToI32(res); + } + + return res; + }, !op.U); + } + + public static void Vrsqrte(ArmEmitterContext context) + { + OpCode32SimdSqrte op = (OpCode32SimdSqrte)context.CurrOp; + + if (op.F) + { + int sizeF = op.Size & 1; + + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FrsqrteV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2 && sizeF == 0) + { + EmitVectorUnaryOpF32(context, Intrinsic.X86Rsqrtps, 0); + } + else + { + EmitVectorUnaryOpF32(context, (op1) => + { + return EmitSoftFloatCallDefaultFpscr(context, nameof(SoftFloat32.FPRSqrtEstimateFpscr), op1); + }); + } + } + else + { + throw new NotImplementedException("Integer Vrsqrte not currently implemented."); + } + } + + public static void Vrsqrts(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FrsqrtsV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + bool single = (op.Size & 1) == 0; + + // (3 - (n*m)) / 2 + EmitVectorBinaryOpSimd32(context, (n, m) => + { + if (single) + { + Operand maskHalf = X86GetAllElements(context, 0.5f); + Operand maskThree = X86GetAllElements(context, 3f); + + Operand res = context.AddIntrinsic(Intrinsic.X86Mulps, n, m); + + res = context.AddIntrinsic(Intrinsic.X86Subps, maskThree, res); + return context.AddIntrinsic(Intrinsic.X86Mulps, maskHalf, res); + } + else + { + Operand maskHalf = X86GetAllElements(context, 0.5d); + Operand maskThree = X86GetAllElements(context, 3d); + + Operand res = context.AddIntrinsic(Intrinsic.X86Mulpd, n, m); + + res = context.AddIntrinsic(Intrinsic.X86Subpd, maskThree, res); + return context.AddIntrinsic(Intrinsic.X86Mulpd, maskHalf, res); + } + }); + } + else + { + EmitVectorBinaryOpF32(context, (op1, op2) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPRSqrtStep), op1, op2); + }); + } + } + + public static void Vsel(ArmEmitterContext context) + { + OpCode32SimdSel op = (OpCode32SimdSel)context.CurrOp; + + Operand condition = default; + + switch (op.Cc) + { + case OpCode32SimdSelMode.Eq: + condition = GetCondTrue(context, Condition.Eq); + break; + case OpCode32SimdSelMode.Ge: + condition = GetCondTrue(context, Condition.Ge); + break; + case OpCode32SimdSelMode.Gt: + condition = GetCondTrue(context, Condition.Gt); + break; + case OpCode32SimdSelMode.Vs: + condition = GetCondTrue(context, Condition.Vs); + break; + } + + EmitScalarBinaryOpI32(context, (op1, op2) => + { + return context.ConditionalSelect(condition, op1, op2); + }); + } + + public static void Vsqrt_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FsqrtS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarUnaryOpF32(context, Intrinsic.X86Sqrtss, Intrinsic.X86Sqrtsd); + } + else + { + EmitScalarUnaryOpF32(context, (op1) => + { + return EmitSoftFloatCall(context, nameof(SoftFloat32.FPSqrt), op1); + }); + } + } + + public static void Vsub_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarBinaryOpF32(context, Intrinsic.Arm64FsubS); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitScalarBinaryOpF32(context, Intrinsic.X86Subss, Intrinsic.X86Subsd); + } + else + { + EmitScalarBinaryOpF32(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Vsub_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpF32(context, Intrinsic.Arm64FsubV); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitVectorBinaryOpF32(context, Intrinsic.X86Subps, Intrinsic.X86Subpd); + } + else + { + EmitVectorBinaryOpF32(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Vsub_I(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + EmitVectorBinaryOpSimd32(context, (op1, op2) => context.AddIntrinsic(X86PsubInstruction[op.Size], op1, op2)); + } + else + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.Subtract(op1, op2)); + } + } + + public static void Vsubl_I(ArmEmitterContext context) + { + OpCode32SimdRegLong op = (OpCode32SimdRegLong)context.CurrOp; + + EmitVectorBinaryLongOpI32(context, (op1, op2) => context.Subtract(op1, op2), !op.U); + } + + public static void Vsubw_I(ArmEmitterContext context) + { + OpCode32SimdRegWide op = (OpCode32SimdRegWide)context.CurrOp; + + EmitVectorBinaryWideOpI32(context, (op1, op2) => context.Subtract(op1, op2), !op.U); + } + + private static void EmitSaturatingAddSubBinaryOp(ArmEmitterContext context, bool add, bool signed) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + EmitVectorBinaryOpI32(context, (ne, me) => + { + if (op.Size <= 2) + { + if (op.Size == 2) + { + ne = signed ? context.SignExtend32(OperandType.I64, ne) : context.ZeroExtend32(OperandType.I64, ne); + me = signed ? context.SignExtend32(OperandType.I64, me) : context.ZeroExtend32(OperandType.I64, me); + } + + Operand res = add ? context.Add(ne, me) : context.Subtract(ne, me); + + res = EmitSatQ(context, res, 8 << op.Size, signedSrc: true, signed); + + if (op.Size == 2) + { + res = context.ConvertI64ToI32(res); + } + + return res; + } + else if (add) /* if (op.Size == 3) */ + { + return signed + ? EmitBinarySignedSatQAdd(context, ne, me) + : EmitBinaryUnsignedSatQAdd(context, ne, me); + } + else /* if (sub) */ + { + return signed + ? EmitBinarySignedSatQSub(context, ne, me) + : EmitBinaryUnsignedSatQSub(context, ne, me); + } + }, signed); + } + + private static void EmitSse41MaxMinNumOpF32(ArmEmitterContext context, bool isMaxNum, bool scalar) + { + IOpCode32Simd op = (IOpCode32Simd)context.CurrOp; + + Func genericEmit = (n, m) => + { + Operand nNum = context.Copy(n); + Operand mNum = context.Copy(m); + + InstEmit.EmitSse2VectorIsNaNOpF(context, nNum, out Operand nQNaNMask, out _, isQNaN: true); + InstEmit.EmitSse2VectorIsNaNOpF(context, mNum, out Operand mQNaNMask, out _, isQNaN: true); + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand negInfMask = X86GetAllElements(context, isMaxNum ? float.NegativeInfinity : float.PositiveInfinity); + + Operand nMask = context.AddIntrinsic(Intrinsic.X86Andnps, mQNaNMask, nQNaNMask); + Operand mMask = context.AddIntrinsic(Intrinsic.X86Andnps, nQNaNMask, mQNaNMask); + + nNum = context.AddIntrinsic(Intrinsic.X86Blendvps, nNum, negInfMask, nMask); + mNum = context.AddIntrinsic(Intrinsic.X86Blendvps, mNum, negInfMask, mMask); + + return context.AddIntrinsic(isMaxNum ? Intrinsic.X86Maxps : Intrinsic.X86Minps, nNum, mNum); + } + else /* if (sizeF == 1) */ + { + Operand negInfMask = X86GetAllElements(context, isMaxNum ? double.NegativeInfinity : double.PositiveInfinity); + + Operand nMask = context.AddIntrinsic(Intrinsic.X86Andnpd, mQNaNMask, nQNaNMask); + Operand mMask = context.AddIntrinsic(Intrinsic.X86Andnpd, nQNaNMask, mQNaNMask); + + nNum = context.AddIntrinsic(Intrinsic.X86Blendvpd, nNum, negInfMask, nMask); + mNum = context.AddIntrinsic(Intrinsic.X86Blendvpd, mNum, negInfMask, mMask); + + return context.AddIntrinsic(isMaxNum ? Intrinsic.X86Maxpd : Intrinsic.X86Minpd, nNum, mNum); + } + }; + + if (scalar) + { + EmitScalarBinaryOpSimd32(context, genericEmit); + } + else + { + EmitVectorBinaryOpSimd32(context, genericEmit); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdCmp.cs b/src/ARMeilleure/Instructions/InstEmitSimdCmp.cs new file mode 100644 index 00000000..c32b64ba --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdCmp.cs @@ -0,0 +1,799 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + using Func2I = Func; + + static partial class InstEmit + { + public static void Cmeq_S(ArmEmitterContext context) + { + EmitCmpOp(context, (op1, op2) => context.ICompareEqual(op1, op2), scalar: true); + } + + public static void Cmeq_V(ArmEmitterContext context) + { + if (Optimizations.UseSse41) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m; + + if (op is OpCodeSimdReg binOp) + { + m = GetVec(binOp.Rm); + } + else + { + m = context.VectorZero(); + } + + Intrinsic cmpInst = X86PcmpeqInstruction[op.Size]; + + Operand res = context.AddIntrinsic(cmpInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitCmpOp(context, (op1, op2) => context.ICompareEqual(op1, op2), scalar: false); + } + } + + public static void Cmge_S(ArmEmitterContext context) + { + EmitCmpOp(context, (op1, op2) => context.ICompareGreaterOrEqual(op1, op2), scalar: true); + } + + public static void Cmge_V(ArmEmitterContext context) + { + if (Optimizations.UseSse42) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m; + + if (op is OpCodeSimdReg binOp) + { + m = GetVec(binOp.Rm); + } + else + { + m = context.VectorZero(); + } + + Intrinsic cmpInst = X86PcmpgtInstruction[op.Size]; + + Operand res = context.AddIntrinsic(cmpInst, m, n); + + Operand mask = X86GetAllElements(context, -1L); + + res = context.AddIntrinsic(Intrinsic.X86Pandn, res, mask); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitCmpOp(context, (op1, op2) => context.ICompareGreaterOrEqual(op1, op2), scalar: false); + } + } + + public static void Cmgt_S(ArmEmitterContext context) + { + EmitCmpOp(context, (op1, op2) => context.ICompareGreater(op1, op2), scalar: true); + } + + public static void Cmgt_V(ArmEmitterContext context) + { + if (Optimizations.UseSse42) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m; + + if (op is OpCodeSimdReg binOp) + { + m = GetVec(binOp.Rm); + } + else + { + m = context.VectorZero(); + } + + Intrinsic cmpInst = X86PcmpgtInstruction[op.Size]; + + Operand res = context.AddIntrinsic(cmpInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitCmpOp(context, (op1, op2) => context.ICompareGreater(op1, op2), scalar: false); + } + } + + public static void Cmhi_S(ArmEmitterContext context) + { + EmitCmpOp(context, (op1, op2) => context.ICompareGreaterUI(op1, op2), scalar: true); + } + + public static void Cmhi_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseSse41 && op.Size < 3) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic maxInst = X86PmaxuInstruction[op.Size]; + + Operand res = context.AddIntrinsic(maxInst, m, n); + + Intrinsic cmpInst = X86PcmpeqInstruction[op.Size]; + + res = context.AddIntrinsic(cmpInst, res, m); + + Operand mask = X86GetAllElements(context, -1L); + + res = context.AddIntrinsic(Intrinsic.X86Pandn, res, mask); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitCmpOp(context, (op1, op2) => context.ICompareGreaterUI(op1, op2), scalar: false); + } + } + + public static void Cmhs_S(ArmEmitterContext context) + { + EmitCmpOp(context, (op1, op2) => context.ICompareGreaterOrEqualUI(op1, op2), scalar: true); + } + + public static void Cmhs_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseSse41 && op.Size < 3) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic maxInst = X86PmaxuInstruction[op.Size]; + + Operand res = context.AddIntrinsic(maxInst, n, m); + + Intrinsic cmpInst = X86PcmpeqInstruction[op.Size]; + + res = context.AddIntrinsic(cmpInst, res, n); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitCmpOp(context, (op1, op2) => context.ICompareGreaterOrEqualUI(op1, op2), scalar: false); + } + } + + public static void Cmle_S(ArmEmitterContext context) + { + EmitCmpOp(context, (op1, op2) => context.ICompareLessOrEqual(op1, op2), scalar: true); + } + + public static void Cmle_V(ArmEmitterContext context) + { + if (Optimizations.UseSse42) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Intrinsic cmpInst = X86PcmpgtInstruction[op.Size]; + + Operand res = context.AddIntrinsic(cmpInst, n, context.VectorZero()); + + Operand mask = X86GetAllElements(context, -1L); + + res = context.AddIntrinsic(Intrinsic.X86Pandn, res, mask); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitCmpOp(context, (op1, op2) => context.ICompareLessOrEqual(op1, op2), scalar: false); + } + } + + public static void Cmlt_S(ArmEmitterContext context) + { + EmitCmpOp(context, (op1, op2) => context.ICompareLess(op1, op2), scalar: true); + } + + public static void Cmlt_V(ArmEmitterContext context) + { + if (Optimizations.UseSse42) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Intrinsic cmpInst = X86PcmpgtInstruction[op.Size]; + + Operand res = context.AddIntrinsic(cmpInst, context.VectorZero(), n); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitCmpOp(context, (op1, op2) => context.ICompareLess(op1, op2), scalar: false); + } + } + + public static void Cmtst_S(ArmEmitterContext context) + { + EmitCmtstOp(context, scalar: true); + } + + public static void Cmtst_V(ArmEmitterContext context) + { + EmitCmtstOp(context, scalar: false); + } + + public static void Facge_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThanOrEqual, scalar: true, absolute: true); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGE), scalar: true, absolute: true); + } + } + + public static void Facge_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThanOrEqual, scalar: false, absolute: true); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGE), scalar: false, absolute: true); + } + } + + public static void Facgt_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThan, scalar: true, absolute: true); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGT), scalar: true, absolute: true); + } + } + + public static void Facgt_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThan, scalar: false, absolute: true); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGT), scalar: false, absolute: true); + } + } + + public static void Fccmp_S(ArmEmitterContext context) + { + EmitFccmpOrFccmpe(context, signalNaNs: false); + } + + public static void Fccmpe_S(ArmEmitterContext context) + { + EmitFccmpOrFccmpe(context, signalNaNs: true); + } + + public static void Fcmeq_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.Equal, scalar: true); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareEQ), scalar: true); + } + } + + public static void Fcmeq_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.Equal, scalar: false); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareEQ), scalar: false); + } + } + + public static void Fcmge_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThanOrEqual, scalar: true); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGE), scalar: true); + } + } + + public static void Fcmge_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThanOrEqual, scalar: false); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGE), scalar: false); + } + } + + public static void Fcmgt_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThan, scalar: true); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGT), scalar: true); + } + } + + public static void Fcmgt_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.GreaterThan, scalar: false); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareGT), scalar: false); + } + } + + public static void Fcmle_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.LessThanOrEqual, scalar: true); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareLE), scalar: true); + } + } + + public static void Fcmle_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.LessThanOrEqual, scalar: false); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareLE), scalar: false); + } + } + + public static void Fcmlt_S(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.LessThan, scalar: true); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareLT), scalar: true); + } + } + + public static void Fcmlt_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2OrAvxCmpOpF(context, CmpCondition.LessThan, scalar: false); + } + else + { + EmitCmpOpF(context, nameof(SoftFloat32.FPCompareLT), scalar: false); + } + } + + public static void Fcmp_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitFcmpOrFcmpe(context, signalNaNs: false); + } + else + { + EmitFcmpOrFcmpe(context, signalNaNs: false); + } + } + + public static void Fcmpe_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitFcmpOrFcmpe(context, signalNaNs: true); + } + else + { + EmitFcmpOrFcmpe(context, signalNaNs: true); + } + } + + private static void EmitFccmpOrFccmpe(ArmEmitterContext context, bool signalNaNs) + { + OpCodeSimdFcond op = (OpCodeSimdFcond)context.CurrOp; + + Operand lblTrue = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblTrue, InstEmitFlowHelper.GetCondTrue(context, op.Cond)); + + EmitSetNzcv(context, op.Nzcv); + + context.Branch(lblEnd); + + context.MarkLabel(lblTrue); + + EmitFcmpOrFcmpe(context, signalNaNs); + + context.MarkLabel(lblEnd); + } + + private static void EmitSetNzcv(ArmEmitterContext context, int nzcv) + { + Operand Extract(int value, int bit) + { + if (bit != 0) + { + value >>= bit; + } + + value &= 1; + + return Const(value); + } + + SetFlag(context, PState.VFlag, Extract(nzcv, 0)); + SetFlag(context, PState.CFlag, Extract(nzcv, 1)); + SetFlag(context, PState.ZFlag, Extract(nzcv, 2)); + SetFlag(context, PState.NFlag, Extract(nzcv, 3)); + } + + private static void EmitFcmpOrFcmpe(ArmEmitterContext context, bool signalNaNs) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + bool cmpWithZero = !(op is OpCodeSimdFcond) ? op.Bit3 : false; + + if (Optimizations.FastFP && (signalNaNs ? Optimizations.UseAvx : Optimizations.UseSse2)) + { + Operand n = GetVec(op.Rn); + Operand m = cmpWithZero ? context.VectorZero() : GetVec(op.Rm); + + CmpCondition cmpOrdered = signalNaNs ? CmpCondition.OrderedS : CmpCondition.OrderedQ; + + Operand lblNaN = Label(); + Operand lblEnd = Label(); + + if (op.Size == 0) + { + Operand ordMask = context.AddIntrinsic(Intrinsic.X86Cmpss, n, m, Const((int)cmpOrdered)); + + Operand isOrdered = context.AddIntrinsicInt(Intrinsic.X86Cvtsi2si, ordMask); + + context.BranchIfFalse(lblNaN, isOrdered); + + Operand nCopy = context.Copy(n); + Operand mCopy = cmpWithZero ? context.VectorZero() : context.Copy(m); + + Operand cf = context.AddIntrinsicInt(Intrinsic.X86Comissge, nCopy, mCopy); + Operand zf = context.AddIntrinsicInt(Intrinsic.X86Comisseq, nCopy, mCopy); + Operand nf = context.AddIntrinsicInt(Intrinsic.X86Comisslt, nCopy, mCopy); + + SetFlag(context, PState.VFlag, Const(0)); + SetFlag(context, PState.CFlag, cf); + SetFlag(context, PState.ZFlag, zf); + SetFlag(context, PState.NFlag, nf); + } + else /* if (op.Size == 1) */ + { + Operand ordMask = context.AddIntrinsic(Intrinsic.X86Cmpsd, n, m, Const((int)cmpOrdered)); + + Operand isOrdered = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, ordMask); + + context.BranchIfFalse(lblNaN, isOrdered); + + Operand nCopy = context.Copy(n); + Operand mCopy = cmpWithZero ? context.VectorZero() : context.Copy(m); + + Operand cf = context.AddIntrinsicInt(Intrinsic.X86Comisdge, nCopy, mCopy); + Operand zf = context.AddIntrinsicInt(Intrinsic.X86Comisdeq, nCopy, mCopy); + Operand nf = context.AddIntrinsicInt(Intrinsic.X86Comisdlt, nCopy, mCopy); + + SetFlag(context, PState.VFlag, Const(0)); + SetFlag(context, PState.CFlag, cf); + SetFlag(context, PState.ZFlag, zf); + SetFlag(context, PState.NFlag, nf); + } + + context.Branch(lblEnd); + + context.MarkLabel(lblNaN); + + SetFlag(context, PState.VFlag, Const(1)); + SetFlag(context, PState.CFlag, Const(1)); + SetFlag(context, PState.ZFlag, Const(0)); + SetFlag(context, PState.NFlag, Const(0)); + + context.MarkLabel(lblEnd); + } + else + { + OperandType type = op.Size != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand ne = context.VectorExtract(type, GetVec(op.Rn), 0); + Operand me; + + if (cmpWithZero) + { + me = op.Size == 0 ? ConstF(0f) : ConstF(0d); + } + else + { + me = context.VectorExtract(type, GetVec(op.Rm), 0); + } + + Operand nzcv = EmitSoftFloatCall(context, nameof(SoftFloat32.FPCompare), ne, me, Const(signalNaNs)); + + EmitSetNzcv(context, nzcv); + } + } + + private static void EmitSetNzcv(ArmEmitterContext context, Operand nzcv) + { + Operand Extract(Operand value, int bit) + { + if (bit != 0) + { + value = context.ShiftRightUI(value, Const(bit)); + } + + value = context.BitwiseAnd(value, Const(1)); + + return value; + } + + SetFlag(context, PState.VFlag, Extract(nzcv, 0)); + SetFlag(context, PState.CFlag, Extract(nzcv, 1)); + SetFlag(context, PState.ZFlag, Extract(nzcv, 2)); + SetFlag(context, PState.NFlag, Extract(nzcv, 3)); + } + + private static void EmitCmpOp(ArmEmitterContext context, Func2I emitCmp, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + ulong szMask = ulong.MaxValue >> (64 - (8 << op.Size)); + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractSx(context, op.Rn, index, op.Size); + Operand me; + + if (op is OpCodeSimdReg binOp) + { + me = EmitVectorExtractSx(context, binOp.Rm, index, op.Size); + } + else + { + me = Const(0L); + } + + Operand isTrue = emitCmp(ne, me); + + Operand mask = context.ConditionalSelect(isTrue, Const(szMask), Const(0L)); + + res = EmitVectorInsert(context, res, mask, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitCmtstOp(ArmEmitterContext context, bool scalar) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + ulong szMask = ulong.MaxValue >> (64 - (8 << op.Size)); + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + Operand me = EmitVectorExtractZx(context, op.Rm, index, op.Size); + + Operand test = context.BitwiseAnd(ne, me); + + Operand isTrue = context.ICompareNotEqual(test, Const(0L)); + + Operand mask = context.ConditionalSelect(isTrue, Const(szMask), Const(0L)); + + res = EmitVectorInsert(context, res, mask, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitCmpOpF(ArmEmitterContext context, string name, bool scalar, bool absolute = false) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = !scalar ? op.GetBytesCount() >> sizeF + 2 : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, GetVec(op.Rn), index); + Operand me; + + if (op is OpCodeSimdReg binOp) + { + me = context.VectorExtract(type, GetVec(binOp.Rm), index); + } + else + { + me = sizeF == 0 ? ConstF(0f) : ConstF(0d); + } + + if (absolute) + { + ne = EmitUnaryMathCall(context, nameof(Math.Abs), ne); + me = EmitUnaryMathCall(context, nameof(Math.Abs), me); + } + + Operand e = EmitSoftFloatCall(context, name, ne, me); + + res = context.VectorInsert(res, e, index); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitSse2OrAvxCmpOpF(ArmEmitterContext context, CmpCondition cond, bool scalar, bool absolute = false) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = op is OpCodeSimdReg binOp ? GetVec(binOp.Rm) : context.VectorZero(); + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + if (absolute) + { + Operand mask = scalar ? X86GetScalar(context, int.MaxValue) : X86GetAllElements(context, int.MaxValue); + + n = context.AddIntrinsic(Intrinsic.X86Andps, n, mask); + m = context.AddIntrinsic(Intrinsic.X86Andps, m, mask); + } + + Intrinsic inst = scalar ? Intrinsic.X86Cmpss : Intrinsic.X86Cmpps; + + Operand res = context.AddIntrinsic(inst, n, m, Const((int)cond)); + + if (scalar) + { + res = context.VectorZeroUpper96(res); + } + else if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + if (absolute) + { + Operand mask = scalar ? X86GetScalar(context, long.MaxValue) : X86GetAllElements(context, long.MaxValue); + + n = context.AddIntrinsic(Intrinsic.X86Andpd, n, mask); + m = context.AddIntrinsic(Intrinsic.X86Andpd, m, mask); + } + + Intrinsic inst = scalar ? Intrinsic.X86Cmpsd : Intrinsic.X86Cmppd; + + Operand res = context.AddIntrinsic(inst, n, m, Const((int)cond)); + + if (scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdCmp32.cs b/src/ARMeilleure/Instructions/InstEmitSimdCmp32.cs new file mode 100644 index 00000000..a990e057 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdCmp32.cs @@ -0,0 +1,437 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper32; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + using Func2I = Func; + + static partial class InstEmit32 + { + public static void Vceq_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.Equal, false); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2OrAvxCmpOpF32(context, CmpCondition.Equal, false); + } + else + { + EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareEQFpscr), false); + } + } + + public static void Vceq_I(ArmEmitterContext context) + { + EmitCmpOpI32(context, context.ICompareEqual, context.ICompareEqual, false, false); + } + + public static void Vceq_Z(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.Equal, true); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2OrAvxCmpOpF32(context, CmpCondition.Equal, true); + } + else + { + EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareEQFpscr), true); + } + } + else + { + EmitCmpOpI32(context, context.ICompareEqual, context.ICompareEqual, true, false); + } + } + + public static void Vcge_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.GreaterThanOrEqual, false); + } + else if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF32(context, CmpCondition.GreaterThanOrEqual, false); + } + else + { + EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareGEFpscr), false); + } + } + + public static void Vcge_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitCmpOpI32(context, context.ICompareGreaterOrEqual, context.ICompareGreaterOrEqualUI, false, !op.U); + } + + public static void Vcge_Z(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.GreaterThanOrEqual, true); + } + else if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF32(context, CmpCondition.GreaterThanOrEqual, true); + } + else + { + EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareGEFpscr), true); + } + } + else + { + EmitCmpOpI32(context, context.ICompareGreaterOrEqual, context.ICompareGreaterOrEqualUI, true, true); + } + } + + public static void Vcgt_V(ArmEmitterContext context) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.GreaterThan, false); + } + else if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF32(context, CmpCondition.GreaterThan, false); + } + else + { + EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareGTFpscr), false); + } + } + + public static void Vcgt_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitCmpOpI32(context, context.ICompareGreater, context.ICompareGreaterUI, false, !op.U); + } + + public static void Vcgt_Z(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.GreaterThan, true); + } + else if (Optimizations.FastFP && Optimizations.UseAvx) + { + EmitSse2OrAvxCmpOpF32(context, CmpCondition.GreaterThan, true); + } + else + { + EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareGTFpscr), true); + } + } + else + { + EmitCmpOpI32(context, context.ICompareGreater, context.ICompareGreaterUI, true, true); + } + } + + public static void Vcle_Z(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.LessThanOrEqual, true); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2OrAvxCmpOpF32(context, CmpCondition.LessThanOrEqual, true); + } + else + { + EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareLEFpscr), true); + } + } + else + { + EmitCmpOpI32(context, context.ICompareLessOrEqual, context.ICompareLessOrEqualUI, true, true); + } + } + + public static void Vclt_Z(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + if (op.F) + { + if (Optimizations.FastFP && Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitCmpOpF32(context, CmpCondition.LessThan, true); + } + else if (Optimizations.FastFP && Optimizations.UseSse2) + { + EmitSse2OrAvxCmpOpF32(context, CmpCondition.LessThan, true); + } + else + { + EmitCmpOpF32(context, nameof(SoftFloat32.FPCompareLTFpscr), true); + } + } + else + { + EmitCmpOpI32(context, context.ICompareLess, context.ICompareLessUI, true, true); + } + } + + private static void EmitCmpOpF32(ArmEmitterContext context, string name, bool zero) + { + if (zero) + { + EmitVectorUnaryOpF32(context, (m) => + { + Operand zeroOp = m.Type == OperandType.FP64 ? ConstF(0.0d) : ConstF(0.0f); + + return EmitSoftFloatCallDefaultFpscr(context, name, m, zeroOp); + }); + } + else + { + EmitVectorBinaryOpF32(context, (n, m) => + { + return EmitSoftFloatCallDefaultFpscr(context, name, n, m); + }); + } + } + + private static Operand ZerosOrOnes(ArmEmitterContext context, Operand fromBool, OperandType baseType) + { + var ones = (baseType == OperandType.I64) ? Const(-1L) : Const(-1); + + return context.ConditionalSelect(fromBool, ones, Const(baseType, 0L)); + } + + private static void EmitCmpOpI32( + ArmEmitterContext context, + Func2I signedOp, + Func2I unsignedOp, + bool zero, + bool signed) + { + if (zero) + { + if (signed) + { + EmitVectorUnaryOpSx32(context, (m) => + { + OperandType type = m.Type; + Operand zeroV = (type == OperandType.I64) ? Const(0L) : Const(0); + + return ZerosOrOnes(context, signedOp(m, zeroV), type); + }); + } + else + { + EmitVectorUnaryOpZx32(context, (m) => + { + OperandType type = m.Type; + Operand zeroV = (type == OperandType.I64) ? Const(0L) : Const(0); + + return ZerosOrOnes(context, unsignedOp(m, zeroV), type); + }); + } + } + else + { + if (signed) + { + EmitVectorBinaryOpSx32(context, (n, m) => ZerosOrOnes(context, signedOp(n, m), n.Type)); + } + else + { + EmitVectorBinaryOpZx32(context, (n, m) => ZerosOrOnes(context, unsignedOp(n, m), n.Type)); + } + } + } + + public static void Vcmp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVcmpOrVcmpe(context, false); + } + else + { + EmitVcmpOrVcmpe(context, false); + } + } + + public static void Vcmpe(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVcmpOrVcmpe(context, true); + } + else + { + EmitVcmpOrVcmpe(context, true); + } + } + + private static void EmitVcmpOrVcmpe(ArmEmitterContext context, bool signalNaNs) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + bool cmpWithZero = (op.Opc & 2) != 0; + int sizeF = op.Size & 1; + + if (Optimizations.FastFP && (signalNaNs ? Optimizations.UseAvx : Optimizations.UseSse2)) + { + CmpCondition cmpOrdered = signalNaNs ? CmpCondition.OrderedS : CmpCondition.OrderedQ; + + bool doubleSize = sizeF != 0; + int shift = doubleSize ? 1 : 2; + Operand m = GetVecA32(op.Vm >> shift); + Operand n = GetVecA32(op.Vd >> shift); + + n = EmitSwapScalar(context, n, op.Vd, doubleSize); + m = cmpWithZero ? context.VectorZero() : EmitSwapScalar(context, m, op.Vm, doubleSize); + + Operand lblNaN = Label(); + Operand lblEnd = Label(); + + if (!doubleSize) + { + Operand ordMask = context.AddIntrinsic(Intrinsic.X86Cmpss, n, m, Const((int)cmpOrdered)); + + Operand isOrdered = context.AddIntrinsicInt(Intrinsic.X86Cvtsi2si, ordMask); + + context.BranchIfFalse(lblNaN, isOrdered); + + Operand cf = context.AddIntrinsicInt(Intrinsic.X86Comissge, n, m); + Operand zf = context.AddIntrinsicInt(Intrinsic.X86Comisseq, n, m); + Operand nf = context.AddIntrinsicInt(Intrinsic.X86Comisslt, n, m); + + SetFpFlag(context, FPState.VFlag, Const(0)); + SetFpFlag(context, FPState.CFlag, cf); + SetFpFlag(context, FPState.ZFlag, zf); + SetFpFlag(context, FPState.NFlag, nf); + } + else + { + Operand ordMask = context.AddIntrinsic(Intrinsic.X86Cmpsd, n, m, Const((int)cmpOrdered)); + + Operand isOrdered = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, ordMask); + + context.BranchIfFalse(lblNaN, isOrdered); + + Operand cf = context.AddIntrinsicInt(Intrinsic.X86Comisdge, n, m); + Operand zf = context.AddIntrinsicInt(Intrinsic.X86Comisdeq, n, m); + Operand nf = context.AddIntrinsicInt(Intrinsic.X86Comisdlt, n, m); + + SetFpFlag(context, FPState.VFlag, Const(0)); + SetFpFlag(context, FPState.CFlag, cf); + SetFpFlag(context, FPState.ZFlag, zf); + SetFpFlag(context, FPState.NFlag, nf); + } + + context.Branch(lblEnd); + + context.MarkLabel(lblNaN); + + SetFpFlag(context, FPState.VFlag, Const(1)); + SetFpFlag(context, FPState.CFlag, Const(1)); + SetFpFlag(context, FPState.ZFlag, Const(0)); + SetFpFlag(context, FPState.NFlag, Const(0)); + + context.MarkLabel(lblEnd); + } + else + { + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand ne = ExtractScalar(context, type, op.Vd); + Operand me; + + if (cmpWithZero) + { + me = sizeF == 0 ? ConstF(0f) : ConstF(0d); + } + else + { + me = ExtractScalar(context, type, op.Vm); + } + + Operand nzcv = EmitSoftFloatCall(context, nameof(SoftFloat32.FPCompare), ne, me, Const(signalNaNs)); + + EmitSetFpscrNzcv(context, nzcv); + } + } + + private static void EmitSetFpscrNzcv(ArmEmitterContext context, Operand nzcv) + { + Operand Extract(Operand value, int bit) + { + if (bit != 0) + { + value = context.ShiftRightUI(value, Const(bit)); + } + + value = context.BitwiseAnd(value, Const(1)); + + return value; + } + + SetFpFlag(context, FPState.VFlag, Extract(nzcv, 0)); + SetFpFlag(context, FPState.CFlag, Extract(nzcv, 1)); + SetFpFlag(context, FPState.ZFlag, Extract(nzcv, 2)); + SetFpFlag(context, FPState.NFlag, Extract(nzcv, 3)); + } + + private static void EmitSse2OrAvxCmpOpF32(ArmEmitterContext context, CmpCondition cond, bool zero) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + int sizeF = op.Size & 1; + Intrinsic inst = (sizeF == 0) ? Intrinsic.X86Cmpps : Intrinsic.X86Cmppd; + + if (zero) + { + EmitVectorUnaryOpSimd32(context, (m) => + { + return context.AddIntrinsic(inst, m, context.VectorZero(), Const((int)cond)); + }); + } + else + { + EmitVectorBinaryOpSimd32(context, (n, m) => + { + return context.AddIntrinsic(inst, n, m, Const((int)cond)); + }); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdCrypto.cs b/src/ARMeilleure/Instructions/InstEmitSimdCrypto.cs new file mode 100644 index 00000000..db24e029 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdCrypto.cs @@ -0,0 +1,99 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Aesd_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Operand res; + + if (Optimizations.UseAesni) + { + res = context.AddIntrinsic(Intrinsic.X86Aesdeclast, context.AddIntrinsic(Intrinsic.X86Xorpd, d, n), context.VectorZero()); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Decrypt)), d, n); + } + + context.Copy(d, res); + } + + public static void Aese_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Operand res; + + if (Optimizations.UseAesni) + { + res = context.AddIntrinsic(Intrinsic.X86Aesenclast, context.AddIntrinsic(Intrinsic.X86Xorpd, d, n), context.VectorZero()); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Encrypt)), d, n); + } + + context.Copy(d, res); + } + + public static void Aesimc_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand res; + + if (Optimizations.UseAesni) + { + res = context.AddIntrinsic(Intrinsic.X86Aesimc, n); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.InverseMixColumns)), n); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void Aesmc_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand res; + + if (Optimizations.UseAesni) + { + Operand roundKey = context.VectorZero(); + + // Inverse Shift Rows, Inverse Sub Bytes, xor 0 so nothing happens + res = context.AddIntrinsic(Intrinsic.X86Aesdeclast, n, roundKey); + + // Shift Rows, Sub Bytes, Mix Columns (!), xor 0 so nothing happens + res = context.AddIntrinsic(Intrinsic.X86Aesenc, res, roundKey); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.MixColumns)), n); + } + + context.Copy(GetVec(op.Rd), res); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdCrypto32.cs b/src/ARMeilleure/Instructions/InstEmitSimdCrypto32.cs new file mode 100644 index 00000000..f713a388 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdCrypto32.cs @@ -0,0 +1,99 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; + +namespace ARMeilleure.Instructions +{ + partial class InstEmit32 + { + public static void Aesd_V(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand d = GetVecA32(op.Qd); + Operand n = GetVecA32(op.Qm); + + Operand res; + + if (Optimizations.UseAesni) + { + res = context.AddIntrinsic(Intrinsic.X86Aesdeclast, context.AddIntrinsic(Intrinsic.X86Xorpd, d, n), context.VectorZero()); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Decrypt)), d, n); + } + + context.Copy(d, res); + } + + public static void Aese_V(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand d = GetVecA32(op.Qd); + Operand n = GetVecA32(op.Qm); + + Operand res; + + if (Optimizations.UseAesni) + { + res = context.AddIntrinsic(Intrinsic.X86Aesenclast, context.AddIntrinsic(Intrinsic.X86Xorpd, d, n), context.VectorZero()); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Encrypt)), d, n); + } + + context.Copy(d, res); + } + + public static void Aesimc_V(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand n = GetVecA32(op.Qm); + + Operand res; + + if (Optimizations.UseAesni) + { + res = context.AddIntrinsic(Intrinsic.X86Aesimc, n); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.InverseMixColumns)), n); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Aesmc_V(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand n = GetVecA32(op.Qm); + + Operand res; + + if (Optimizations.UseAesni) + { + Operand roundKey = context.VectorZero(); + + // Inverse Shift Rows, Inverse Sub Bytes, xor 0 so nothing happens. + res = context.AddIntrinsic(Intrinsic.X86Aesdeclast, n, roundKey); + + // Shift Rows, Sub Bytes, Mix Columns (!), xor 0 so nothing happens. + res = context.AddIntrinsic(Intrinsic.X86Aesenc, res, roundKey); + } + else + { + res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.MixColumns)), n); + } + + context.Copy(GetVecA32(op.Qd), res); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdCvt.cs b/src/ARMeilleure/Instructions/InstEmitSimdCvt.cs new file mode 100644 index 00000000..652ad397 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdCvt.cs @@ -0,0 +1,1891 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using System.Reflection; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + using Func1I = Func; + + static partial class InstEmit + { + public static void Fcvt_S(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + if (op.Size == 0 && op.Opc == 1) // Single -> Double. + { + if (Optimizations.UseSse2) + { + Operand n = GetVec(op.Rn); + + Operand res = context.AddIntrinsic(Intrinsic.X86Cvtss2sd, context.VectorZero(), n); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand ne = context.VectorExtract(OperandType.FP32, GetVec(op.Rn), 0); + + Operand res = context.ConvertToFP(OperandType.FP64, ne); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + else if (op.Size == 1 && op.Opc == 0) // Double -> Single. + { + if (Optimizations.UseSse2) + { + Operand n = GetVec(op.Rn); + + Operand res = context.AddIntrinsic(Intrinsic.X86Cvtsd2ss, context.VectorZero(), n); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand ne = context.VectorExtract(OperandType.FP64, GetVec(op.Rn), 0); + + Operand res = context.ConvertToFP(OperandType.FP32, ne); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + else if (op.Size == 0 && op.Opc == 3) // Single -> Half. + { + if (Optimizations.UseF16c) + { + Debug.Assert(!Optimizations.ForceLegacySse); + + Operand n = GetVec(op.Rn); + + Operand res = context.AddIntrinsic(Intrinsic.X86Vcvtps2ph, n, Const(X86GetRoundControl(FPRoundingMode.ToNearest))); + res = context.AddIntrinsic(Intrinsic.X86Pslldq, res, Const(14)); // VectorZeroUpper112() + res = context.AddIntrinsic(Intrinsic.X86Psrldq, res, Const(14)); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand ne = context.VectorExtract(OperandType.FP32, GetVec(op.Rn), 0); + + context.StoreToContext(); + Operand res = context.Call(typeof(SoftFloat32_16).GetMethod(nameof(SoftFloat32_16.FPConvert)), ne); + context.LoadFromContext(); + + res = context.ZeroExtend16(OperandType.I64, res); + + context.Copy(GetVec(op.Rd), EmitVectorInsert(context, context.VectorZero(), res, 0, 1)); + } + } + else if (op.Size == 3 && op.Opc == 0) // Half -> Single. + { + if (Optimizations.UseF16c) + { + Debug.Assert(!Optimizations.ForceLegacySse); + + Operand res = context.AddIntrinsic(Intrinsic.X86Vcvtph2ps, GetVec(op.Rn)); + res = context.VectorZeroUpper96(res); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand ne = EmitVectorExtractZx(context, op.Rn, 0, 1); + + context.StoreToContext(); + Operand res = context.Call(typeof(SoftFloat16_32).GetMethod(nameof(SoftFloat16_32.FPConvert)), ne); + context.LoadFromContext(); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + else if (op.Size == 1 && op.Opc == 3) // Double -> Half. + { + if (Optimizations.UseF16c) + { + Debug.Assert(!Optimizations.ForceLegacySse); + + Operand n = GetVec(op.Rn); + + Operand res = context.AddIntrinsic(Intrinsic.X86Cvtsd2ss, context.VectorZero(), n); + res = context.AddIntrinsic(Intrinsic.X86Vcvtps2ph, res, Const(X86GetRoundControl(FPRoundingMode.ToNearest))); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand ne = context.VectorExtract(OperandType.FP64, GetVec(op.Rn), 0); + + context.StoreToContext(); + Operand res = context.Call(typeof(SoftFloat64_16).GetMethod(nameof(SoftFloat64_16.FPConvert)), ne); + context.LoadFromContext(); + + res = context.ZeroExtend16(OperandType.I64, res); + + context.Copy(GetVec(op.Rd), EmitVectorInsert(context, context.VectorZero(), res, 0, 1)); + } + } + else if (op.Size == 3 && op.Opc == 1) // Half -> Double. + { + if (Optimizations.UseF16c) + { + Operand n = GetVec(op.Rn); + + Operand res = context.AddIntrinsic(Intrinsic.X86Vcvtph2ps, GetVec(op.Rn)); + res = context.AddIntrinsic(Intrinsic.X86Cvtss2sd, context.VectorZero(), res); + res = context.VectorZeroUpper64(res); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand ne = EmitVectorExtractZx(context, op.Rn, 0, 1); + + context.StoreToContext(); + Operand res = context.Call(typeof(SoftFloat16_64).GetMethod(nameof(SoftFloat16_64.FPConvert)), ne); + context.LoadFromContext(); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + else // Invalid encoding. + { + Debug.Assert(false, $"type == {op.Size} && opc == {op.Opc}"); + } + } + + public static void Fcvtas_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtasGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.ToNearestAway, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1)); + } + } + + public static void Fcvtas_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FcvtasS); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.ToNearestAway, scalar: true); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1), signed: true, scalar: true); + } + } + + public static void Fcvtas_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtasS); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.ToNearestAway, scalar: false); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1), signed: true, scalar: false); + } + } + + public static void Fcvtau_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtauGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.ToNearestAway, isFixed: false); + } + else + { + EmitFcvt_u_Gp(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1)); + } + } + + public static void Fcvtau_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FcvtauS); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtuOpF(context, FPRoundingMode.ToNearestAway, scalar: true); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1), signed: false, scalar: true); + } + } + + public static void Fcvtau_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtauV); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtuOpF(context, FPRoundingMode.ToNearestAway, scalar: false); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, op1), signed: false, scalar: false); + } + } + + public static void Fcvtl_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtlV); + } + else if (Optimizations.UseSse2 && sizeF == 1) + { + Operand n = GetVec(op.Rn); + + Operand res = op.RegisterSize == RegisterSize.Simd128 ? context.AddIntrinsic(Intrinsic.X86Movhlps, n, n) : n; + res = context.AddIntrinsic(Intrinsic.X86Cvtps2pd, res); + + context.Copy(GetVec(op.Rd), res); + } + else if (Optimizations.UseF16c && sizeF == 0) + { + Debug.Assert(!Optimizations.ForceLegacySse); + + Operand n = GetVec(op.Rn); + + Operand res = op.RegisterSize == RegisterSize.Simd128 ? context.AddIntrinsic(Intrinsic.X86Movhlps, n, n) : n; + res = context.AddIntrinsic(Intrinsic.X86Vcvtph2ps, res); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand res = context.VectorZero(); + + int elems = 4 >> sizeF; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + for (int index = 0; index < elems; index++) + { + if (sizeF == 0) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, part + index, 1); + + context.StoreToContext(); + Operand e = context.Call(typeof(SoftFloat16_32).GetMethod(nameof(SoftFloat16_32.FPConvert)), ne); + context.LoadFromContext(); + + res = context.VectorInsert(res, e, index); + } + else /* if (sizeF == 1) */ + { + Operand ne = context.VectorExtract(OperandType.FP32, GetVec(op.Rn), part + index); + + Operand e = context.ConvertToFP(OperandType.FP64, ne); + + res = context.VectorInsert(res, e, index); + } + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Fcvtms_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtmsGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsMinusInfinity, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Floor), op1)); + } + } + + public static void Fcvtms_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtmsV); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.TowardsMinusInfinity, scalar: false); + } + else + { + EmitFcvt(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Floor), op1), signed: true, scalar: false); + } + } + + public static void Fcvtmu_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtmuGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsMinusInfinity, isFixed: false); + } + else + { + EmitFcvt_u_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Floor), op1)); + } + } + + public static void Fcvtn_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int sizeF = op.Size & 1; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOpFRd(context, Intrinsic.Arm64FcvtnV); + } + else if (Optimizations.UseSse2 && sizeF == 1) + { + Operand d = GetVec(op.Rd); + + Intrinsic movInst = op.RegisterSize == RegisterSize.Simd128 ? Intrinsic.X86Movlhps : Intrinsic.X86Movhlps; + + Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtpd2ps, GetVec(op.Rn)); + nInt = context.AddIntrinsic(Intrinsic.X86Movlhps, nInt, nInt); + + Operand res = context.VectorZeroUpper64(d); + res = context.AddIntrinsic(movInst, res, nInt); + + context.Copy(d, res); + } + else if (Optimizations.UseF16c && sizeF == 0) + { + Debug.Assert(!Optimizations.ForceLegacySse); + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Intrinsic movInst = op.RegisterSize == RegisterSize.Simd128 ? Intrinsic.X86Movlhps : Intrinsic.X86Movhlps; + + Operand nInt = context.AddIntrinsic(Intrinsic.X86Vcvtps2ph, n, Const(X86GetRoundControl(FPRoundingMode.ToNearest))); + nInt = context.AddIntrinsic(Intrinsic.X86Movlhps, nInt, nInt); + + Operand res = context.VectorZeroUpper64(d); + res = context.AddIntrinsic(movInst, res, nInt); + + context.Copy(d, res); + } + else + { + OperandType type = sizeF == 0 ? OperandType.FP32 : OperandType.FP64; + + int elems = 4 >> sizeF; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, GetVec(op.Rn), index); + + if (sizeF == 0) + { + context.StoreToContext(); + Operand e = context.Call(typeof(SoftFloat32_16).GetMethod(nameof(SoftFloat32_16.FPConvert)), ne); + context.LoadFromContext(); + + res = EmitVectorInsert(context, res, e, part + index, 1); + } + else /* if (sizeF == 1) */ + { + Operand e = context.ConvertToFP(OperandType.FP32, ne); + + res = context.VectorInsert(res, e, part + index); + } + } + + context.Copy(d, res); + } + } + + public static void Fcvtns_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtnsGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.ToNearest, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => EmitRoundMathCall(context, MidpointRounding.ToEven, op1)); + } + } + + public static void Fcvtns_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FcvtnsS); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.ToNearest, scalar: true); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.ToEven, op1), signed: true, scalar: true); + } + } + + public static void Fcvtns_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtnsV); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.ToNearest, scalar: false); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.ToEven, op1), signed: true, scalar: false); + } + } + + public static void Fcvtnu_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FcvtnuS); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtuOpF(context, FPRoundingMode.ToNearest, scalar: true); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.ToEven, op1), signed: false, scalar: true); + } + } + + public static void Fcvtnu_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtnuV); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtuOpF(context, FPRoundingMode.ToNearest, scalar: false); + } + else + { + EmitFcvt(context, (op1) => EmitRoundMathCall(context, MidpointRounding.ToEven, op1), signed: false, scalar: false); + } + } + + public static void Fcvtps_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtpsGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsPlusInfinity, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Ceiling), op1)); + } + } + + public static void Fcvtpu_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtpuGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsPlusInfinity, isFixed: false); + } + else + { + EmitFcvt_u_Gp(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Ceiling), op1)); + } + } + + public static void Fcvtzs_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtzsGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsZero, isFixed: false); + } + else + { + EmitFcvt_s_Gp(context, (op1) => op1); + } + } + + public static void Fcvtzs_Gp_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + InstEmitSimdHelperArm64.EmitScalarConvertBinaryOpFToGp(context, Intrinsic.Arm64FcvtzsGpFixed, op.FBits); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvts_Gp(context, FPRoundingMode.TowardsZero, isFixed: true); + } + else + { + EmitFcvtzs_Gp_Fixed(context); + } + } + + public static void Fcvtzs_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FcvtzsS); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.TowardsZero, scalar: true); + } + else + { + EmitFcvtz(context, signed: true, scalar: true); + } + } + + public static void Fcvtzs_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtzsV); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.TowardsZero, scalar: false); + } + else + { + EmitFcvtz(context, signed: true, scalar: false); + } + } + + public static void Fcvtzs_V_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorConvertBinaryOpF(context, Intrinsic.Arm64FcvtzsVFixed, GetFBits(context)); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtsOpF(context, FPRoundingMode.TowardsZero, scalar: false); + } + else + { + EmitFcvtz(context, signed: true, scalar: false); + } + } + + public static void Fcvtzu_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFToGp(context, Intrinsic.Arm64FcvtzuGp); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsZero, isFixed: false); + } + else + { + EmitFcvt_u_Gp(context, (op1) => op1); + } + } + + public static void Fcvtzu_Gp_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + InstEmitSimdHelperArm64.EmitScalarConvertBinaryOpFToGp(context, Intrinsic.Arm64FcvtzuGpFixed, op.FBits); + } + else if (Optimizations.UseSse41) + { + EmitSse41Fcvtu_Gp(context, FPRoundingMode.TowardsZero, isFixed: true); + } + else + { + EmitFcvtzu_Gp_Fixed(context); + } + } + + public static void Fcvtzu_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64FcvtzuS); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtuOpF(context, FPRoundingMode.TowardsZero, scalar: true); + } + else + { + EmitFcvtz(context, signed: false, scalar: true); + } + } + + public static void Fcvtzu_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64FcvtzuV); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtuOpF(context, FPRoundingMode.TowardsZero, scalar: false); + } + else + { + EmitFcvtz(context, signed: false, scalar: false); + } + } + + public static void Fcvtzu_V_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorConvertBinaryOpF(context, Intrinsic.Arm64FcvtzuVFixed, GetFBits(context)); + } + else if (Optimizations.UseSse41) + { + EmitSse41FcvtuOpF(context, FPRoundingMode.TowardsZero, scalar: false); + } + else + { + EmitFcvtz(context, signed: false, scalar: false); + } + } + + public static void Scvtf_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFFromGp(context, Intrinsic.Arm64ScvtfGp); + } + else + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + Operand res = GetIntOrZR(context, op.Rn); + + if (op.RegisterSize == RegisterSize.Int32) + { + res = context.SignExtend32(OperandType.I64, res); + } + + res = EmitFPConvert(context, res, op.Size, signed: true); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + + public static void Scvtf_Gp_Fixed(ArmEmitterContext context) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarConvertBinaryOpFFromGp(context, Intrinsic.Arm64ScvtfGpFixed, op.FBits); + } + else + { + Operand res = GetIntOrZR(context, op.Rn); + + if (op.RegisterSize == RegisterSize.Int32) + { + res = context.SignExtend32(OperandType.I64, res); + } + + res = EmitFPConvert(context, res, op.Size, signed: true); + + res = EmitI2fFBitsMul(context, res, op.FBits); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + + public static void Scvtf_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64ScvtfS); + } + else if (Optimizations.UseSse2) + { + EmitSse2ScvtfOp(context, scalar: true); + } + else + { + EmitCvtf(context, signed: true, scalar: true); + } + } + + public static void Scvtf_S_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarConvertBinaryOpF(context, Intrinsic.Arm64ScvtfSFixed, GetFBits(context)); + } + else if (Optimizations.UseSse2) + { + EmitSse2ScvtfOp(context, scalar: true); + } + else + { + EmitCvtf(context, signed: true, scalar: true); + } + } + + public static void Scvtf_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64ScvtfV); + } + else if (Optimizations.UseSse2) + { + EmitSse2ScvtfOp(context, scalar: false); + } + else + { + EmitCvtf(context, signed: true, scalar: false); + } + } + + public static void Scvtf_V_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorConvertBinaryOpF(context, Intrinsic.Arm64ScvtfVFixed, GetFBits(context)); + } + else if (Optimizations.UseSse2) + { + EmitSse2ScvtfOp(context, scalar: false); + } + else + { + EmitCvtf(context, signed: true, scalar: false); + } + } + + public static void Ucvtf_Gp(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpFFromGp(context, Intrinsic.Arm64UcvtfGp); + } + else + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + Operand res = GetIntOrZR(context, op.Rn); + + res = EmitFPConvert(context, res, op.Size, signed: false); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + + public static void Ucvtf_Gp_Fixed(ArmEmitterContext context) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarConvertBinaryOpFFromGp(context, Intrinsic.Arm64UcvtfGpFixed, op.FBits); + } + else + { + Operand res = GetIntOrZR(context, op.Rn); + + res = EmitFPConvert(context, res, op.Size, signed: false); + + res = EmitI2fFBitsMul(context, res, op.FBits); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), res, 0)); + } + } + + public static void Ucvtf_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarUnaryOpF(context, Intrinsic.Arm64UcvtfS); + } + else if (Optimizations.UseSse2) + { + EmitSse2UcvtfOp(context, scalar: true); + } + else + { + EmitCvtf(context, signed: false, scalar: true); + } + } + + public static void Ucvtf_S_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarConvertBinaryOpF(context, Intrinsic.Arm64UcvtfSFixed, GetFBits(context)); + } + else if (Optimizations.UseSse2) + { + EmitSse2UcvtfOp(context, scalar: true); + } + else + { + EmitCvtf(context, signed: false, scalar: true); + } + } + + public static void Ucvtf_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOpF(context, Intrinsic.Arm64UcvtfV); + } + else if (Optimizations.UseSse2) + { + EmitSse2UcvtfOp(context, scalar: false); + } + else + { + EmitCvtf(context, signed: false, scalar: false); + } + } + + public static void Ucvtf_V_Fixed(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorConvertBinaryOpF(context, Intrinsic.Arm64UcvtfVFixed, GetFBits(context)); + } + else if (Optimizations.UseSse2) + { + EmitSse2UcvtfOp(context, scalar: false); + } + else + { + EmitCvtf(context, signed: false, scalar: false); + } + } + + private static void EmitFcvt(ArmEmitterContext context, Func1I emit, bool signed, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + Operand n = GetVec(op.Rn); + + int sizeF = op.Size & 1; + int sizeI = sizeF + 2; + + OperandType type = sizeF == 0 ? OperandType.FP32 : OperandType.FP64; + + int elems = !scalar ? op.GetBytesCount() >> sizeI : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, n, index); + + Operand e = emit(ne); + + if (sizeF == 0) + { + MethodInfo info = signed + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS32)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU32)); + + e = context.Call(info, e); + + e = context.ZeroExtend32(OperandType.I64, e); + } + else /* if (sizeF == 1) */ + { + MethodInfo info = signed + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToS64)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToU64)); + + e = context.Call(info, e); + } + + res = EmitVectorInsert(context, res, e, index, sizeI); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitFcvtz(ArmEmitterContext context, bool signed, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + Operand n = GetVec(op.Rn); + + int sizeF = op.Size & 1; + int sizeI = sizeF + 2; + + OperandType type = sizeF == 0 ? OperandType.FP32 : OperandType.FP64; + + int fBits = GetFBits(context); + + int elems = !scalar ? op.GetBytesCount() >> sizeI : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, n, index); + + Operand e = EmitF2iFBitsMul(context, ne, fBits); + + if (sizeF == 0) + { + MethodInfo info = signed + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS32)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU32)); + + e = context.Call(info, e); + + e = context.ZeroExtend32(OperandType.I64, e); + } + else /* if (sizeF == 1) */ + { + MethodInfo info = signed + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToS64)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToU64)); + + e = context.Call(info, e); + } + + res = EmitVectorInsert(context, res, e, index, sizeI); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitFcvt_s_Gp(ArmEmitterContext context, Func1I emit) + { + EmitFcvt___Gp(context, emit, signed: true); + } + + private static void EmitFcvt_u_Gp(ArmEmitterContext context, Func1I emit) + { + EmitFcvt___Gp(context, emit, signed: false); + } + + private static void EmitFcvt___Gp(ArmEmitterContext context, Func1I emit, bool signed) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + OperandType type = op.Size == 0 ? OperandType.FP32 : OperandType.FP64; + + Operand ne = context.VectorExtract(type, GetVec(op.Rn), 0); + + Operand res = signed + ? EmitScalarFcvts(context, emit(ne), 0) + : EmitScalarFcvtu(context, emit(ne), 0); + + SetIntOrZR(context, op.Rd, res); + } + + private static void EmitFcvtzs_Gp_Fixed(ArmEmitterContext context) + { + EmitFcvtz__Gp_Fixed(context, signed: true); + } + + private static void EmitFcvtzu_Gp_Fixed(ArmEmitterContext context) + { + EmitFcvtz__Gp_Fixed(context, signed: false); + } + + private static void EmitFcvtz__Gp_Fixed(ArmEmitterContext context, bool signed) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + OperandType type = op.Size == 0 ? OperandType.FP32 : OperandType.FP64; + + Operand ne = context.VectorExtract(type, GetVec(op.Rn), 0); + + Operand res = signed + ? EmitScalarFcvts(context, ne, op.FBits) + : EmitScalarFcvtu(context, ne, op.FBits); + + SetIntOrZR(context, op.Rd, res); + } + + private static void EmitCvtf(ArmEmitterContext context, bool signed, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + int sizeI = sizeF + 2; + + int fBits = GetFBits(context); + + int elems = !scalar ? op.GetBytesCount() >> sizeI : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorLongExtract(context, op.Rn, index, sizeI); + + Operand e = EmitFPConvert(context, ne, sizeF, signed); + + e = EmitI2fFBitsMul(context, e, fBits); + + res = context.VectorInsert(res, e, index); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static int GetFBits(ArmEmitterContext context) + { + if (context.CurrOp is OpCodeSimdShImm op) + { + return GetImmShr(op); + } + + return 0; + } + + private static Operand EmitFPConvert(ArmEmitterContext context, Operand value, int size, bool signed) + { + Debug.Assert(value.Type == OperandType.I32 || value.Type == OperandType.I64); + Debug.Assert((uint)size < 2); + + OperandType type = size == 0 ? OperandType.FP32 : OperandType.FP64; + + if (signed) + { + return context.ConvertToFP(type, value); + } + else + { + return context.ConvertToFPUI(type, value); + } + } + + private static Operand EmitScalarFcvts(ArmEmitterContext context, Operand value, int fBits) + { + Debug.Assert(value.Type == OperandType.FP32 || value.Type == OperandType.FP64); + + value = EmitF2iFBitsMul(context, value, fBits); + + MethodInfo info; + + if (context.CurrOp.RegisterSize == RegisterSize.Int32) + { + info = value.Type == OperandType.FP32 + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS32)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToS32)); + } + else + { + info = value.Type == OperandType.FP32 + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS64)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToS64)); + } + + return context.Call(info, value); + } + + private static Operand EmitScalarFcvtu(ArmEmitterContext context, Operand value, int fBits) + { + Debug.Assert(value.Type == OperandType.FP32 || value.Type == OperandType.FP64); + + value = EmitF2iFBitsMul(context, value, fBits); + + MethodInfo info; + + if (context.CurrOp.RegisterSize == RegisterSize.Int32) + { + info = value.Type == OperandType.FP32 + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU32)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToU32)); + } + else + { + info = value.Type == OperandType.FP32 + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU64)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToU64)); + } + + return context.Call(info, value); + } + + private static Operand EmitF2iFBitsMul(ArmEmitterContext context, Operand value, int fBits) + { + Debug.Assert(value.Type == OperandType.FP32 || value.Type == OperandType.FP64); + + if (fBits == 0) + { + return value; + } + + if (value.Type == OperandType.FP32) + { + return context.Multiply(value, ConstF(MathF.Pow(2f, fBits))); + } + else /* if (value.Type == OperandType.FP64) */ + { + return context.Multiply(value, ConstF(Math.Pow(2d, fBits))); + } + } + + private static Operand EmitI2fFBitsMul(ArmEmitterContext context, Operand value, int fBits) + { + Debug.Assert(value.Type == OperandType.FP32 || value.Type == OperandType.FP64); + + if (fBits == 0) + { + return value; + } + + if (value.Type == OperandType.FP32) + { + return context.Multiply(value, ConstF(1f / MathF.Pow(2f, fBits))); + } + else /* if (value.Type == OperandType.FP64) */ + { + return context.Multiply(value, ConstF(1d / Math.Pow(2d, fBits))); + } + } + + public static Operand EmitSse2CvtDoubleToInt64OpF(ArmEmitterContext context, Operand opF, bool scalar) + { + Debug.Assert(opF.Type == OperandType.V128); + + Operand longL = context.AddIntrinsicLong (Intrinsic.X86Cvtsd2si, opF); // opFL + Operand res = context.VectorCreateScalar(longL); + + if (!scalar) + { + Operand opFH = context.AddIntrinsic (Intrinsic.X86Movhlps, res, opF); // res doesn't matter. + Operand longH = context.AddIntrinsicLong (Intrinsic.X86Cvtsd2si, opFH); + Operand resH = context.VectorCreateScalar(longH); + res = context.AddIntrinsic (Intrinsic.X86Movlhps, res, resH); + } + + return res; + } + + private static Operand EmitSse2CvtInt64ToDoubleOp(ArmEmitterContext context, Operand op, bool scalar) + { + Debug.Assert(op.Type == OperandType.V128); + + Operand longL = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, op); // opL + Operand res = context.AddIntrinsic (Intrinsic.X86Cvtsi2sd, context.VectorZero(), longL); + + if (!scalar) + { + Operand opH = context.AddIntrinsic (Intrinsic.X86Movhlps, res, op); // res doesn't matter. + Operand longH = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, opH); + Operand resH = context.AddIntrinsic (Intrinsic.X86Cvtsi2sd, res, longH); // res doesn't matter. + res = context.AddIntrinsic (Intrinsic.X86Movlhps, res, resH); + } + + return res; + } + + private static void EmitSse2ScvtfOp(ArmEmitterContext context, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + // sizeF == ((OpCodeSimdShImm)op).Size - 2 + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand res = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, n); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int32BitsToSingle(fpScaled) == 1f / MathF.Pow(2f, fBits) + int fpScaled = 0x3F800000 - fBits * 0x800000; + + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); + + res = context.AddIntrinsic(Intrinsic.X86Mulps, res, fpScaledMask); + } + + if (scalar) + { + res = context.VectorZeroUpper96(res); + } + else if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + Operand res = EmitSse2CvtInt64ToDoubleOp(context, n, scalar); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int64BitsToDouble(fpScaled) == 1d / Math.Pow(2d, fBits) + long fpScaled = 0x3FF0000000000000L - fBits * 0x10000000000000L; + + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); + + res = context.AddIntrinsic(Intrinsic.X86Mulpd, res, fpScaledMask); + } + + if (scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static void EmitSse2UcvtfOp(ArmEmitterContext context, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + // sizeF == ((OpCodeSimdShImm)op).Size - 2 + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand mask = scalar // 65536.000f (1 << 16) + ? X86GetScalar (context, 0x47800000) + : X86GetAllElements(context, 0x47800000); + + Operand res = context.AddIntrinsic(Intrinsic.X86Psrld, n, Const(16)); + res = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, res); + res = context.AddIntrinsic(Intrinsic.X86Mulps, res, mask); + + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pslld, n, Const(16)); + res2 = context.AddIntrinsic(Intrinsic.X86Psrld, res2, Const(16)); + res2 = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, res2); + + res = context.AddIntrinsic(Intrinsic.X86Addps, res, res2); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int32BitsToSingle(fpScaled) == 1f / MathF.Pow(2f, fBits) + int fpScaled = 0x3F800000 - fBits * 0x800000; + + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); + + res = context.AddIntrinsic(Intrinsic.X86Mulps, res, fpScaledMask); + } + + if (scalar) + { + res = context.VectorZeroUpper96(res); + } + else if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else /* if (sizeF == 1) */ + { + Operand mask = scalar // 4294967296.0000000d (1L << 32) + ? X86GetScalar (context, 0x41F0000000000000L) + : X86GetAllElements(context, 0x41F0000000000000L); + + Operand res = context.AddIntrinsic (Intrinsic.X86Psrlq, n, Const(32)); + res = EmitSse2CvtInt64ToDoubleOp(context, res, scalar); + res = context.AddIntrinsic (Intrinsic.X86Mulpd, res, mask); + + Operand res2 = context.AddIntrinsic (Intrinsic.X86Psllq, n, Const(32)); + res2 = context.AddIntrinsic (Intrinsic.X86Psrlq, res2, Const(32)); + res2 = EmitSse2CvtInt64ToDoubleOp(context, res2, scalar); + + res = context.AddIntrinsic(Intrinsic.X86Addpd, res, res2); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int64BitsToDouble(fpScaled) == 1d / Math.Pow(2d, fBits) + long fpScaled = 0x3FF0000000000000L - fBits * 0x10000000000000L; + + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); + + res = context.AddIntrinsic(Intrinsic.X86Mulpd, res, fpScaledMask); + } + + if (scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static void EmitSse41FcvtsOpF(ArmEmitterContext context, FPRoundingMode roundMode, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + // sizeF == ((OpCodeSimdShImm)op).Size - 2 + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int32BitsToSingle(fpScaled) == MathF.Pow(2f, fBits) + int fpScaled = 0x3F800000 + fBits * 0x800000; + + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulps, nRes, fpScaledMask); + } + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundps, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar); + } + + Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRes); + + Operand fpMaxValMask = scalar // 2.14748365E9f (2147483648) + ? X86GetScalar (context, 0x4F000000) + : X86GetAllElements(context, 0x4F000000); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nInt, nRes); + + if (scalar) + { + dRes = context.VectorZeroUpper96(dRes); + } + else if (op.RegisterSize == RegisterSize.Simd64) + { + dRes = context.VectorZeroUpper64(dRes); + } + + context.Copy(GetVec(op.Rd), dRes); + } + else /* if (sizeF == 1) */ + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int64BitsToDouble(fpScaled) == Math.Pow(2d, fBits) + long fpScaled = 0x3FF0000000000000L + fBits * 0x10000000000000L; + + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulpd, nRes, fpScaledMask); + } + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundpd, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar); + } + + Operand nLong = EmitSse2CvtDoubleToInt64OpF(context, nRes, scalar); + + Operand fpMaxValMask = scalar // 9.2233720368547760E18d (9223372036854775808) + ? X86GetScalar (context, 0x43E0000000000000L) + : X86GetAllElements(context, 0x43E0000000000000L); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nLong, nRes); + + if (scalar) + { + dRes = context.VectorZeroUpper64(dRes); + } + + context.Copy(GetVec(op.Rd), dRes); + } + } + + private static void EmitSse41FcvtuOpF(ArmEmitterContext context, FPRoundingMode roundMode, bool scalar) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + // sizeF == ((OpCodeSimdShImm)op).Size - 2 + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int32BitsToSingle(fpScaled) == MathF.Pow(2f, fBits) + int fpScaled = 0x3F800000 + fBits * 0x800000; + + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulps, nRes, fpScaledMask); + } + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundps, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar); + } + + Operand zero = context.VectorZero(); + + Operand nCmp = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + Operand fpMaxValMask = scalar // 2.14748365E9f (2147483648) + ? X86GetScalar (context, 0x4F000000) + : X86GetAllElements(context, 0x4F000000); + + Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Subps, nRes, fpMaxValMask); + + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + Operand nInt2 = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nInt2, nRes); + dRes = context.AddIntrinsic(Intrinsic.X86Paddd, dRes, nInt); + + if (scalar) + { + dRes = context.VectorZeroUpper96(dRes); + } + else if (op.RegisterSize == RegisterSize.Simd64) + { + dRes = context.VectorZeroUpper64(dRes); + } + + context.Copy(GetVec(op.Rd), dRes); + } + else /* if (sizeF == 1) */ + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (op is OpCodeSimdShImm fixedOp) + { + int fBits = GetImmShr(fixedOp); + + // BitConverter.Int64BitsToDouble(fpScaled) == Math.Pow(2d, fBits) + long fpScaled = 0x3FF0000000000000L + fBits * 0x10000000000000L; + + Operand fpScaledMask = scalar + ? X86GetScalar (context, fpScaled) + : X86GetAllElements(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulpd, nRes, fpScaledMask); + } + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundpd, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar); + } + + Operand zero = context.VectorZero(); + + Operand nCmp = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + Operand fpMaxValMask = scalar // 9.2233720368547760E18d (9223372036854775808) + ? X86GetScalar (context, 0x43E0000000000000L) + : X86GetAllElements(context, 0x43E0000000000000L); + + Operand nLong = EmitSse2CvtDoubleToInt64OpF(context, nRes, scalar); + + nRes = context.AddIntrinsic(Intrinsic.X86Subpd, nRes, fpMaxValMask); + + nCmp = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + Operand nLong2 = EmitSse2CvtDoubleToInt64OpF(context, nRes, scalar); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nLong2, nRes); + dRes = context.AddIntrinsic(Intrinsic.X86Paddq, dRes, nLong); + + if (scalar) + { + dRes = context.VectorZeroUpper64(dRes); + } + + context.Copy(GetVec(op.Rd), dRes); + } + } + + private static void EmitSse41Fcvts_Gp(ArmEmitterContext context, FPRoundingMode roundMode, bool isFixed) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if (op.Size == 0) + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (isFixed) + { + // BitConverter.Int32BitsToSingle(fpScaled) == MathF.Pow(2f, op.FBits) + int fpScaled = 0x3F800000 + op.FBits * 0x800000; + + Operand fpScaledMask = X86GetScalar(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulss, nRes, fpScaledMask); + } + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundss, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar: true); + } + + Operand nIntOrLong = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt (Intrinsic.X86Cvtss2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtss2si, nRes); + + int fpMaxVal = op.RegisterSize == RegisterSize.Int32 + ? 0x4F000000 // 2.14748365E9f (2147483648) + : 0x5F000000; // 9.223372E18f (9223372036854775808) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nInt = context.AddIntrinsicInt(Intrinsic.X86Cvtsi2si, nRes); + + if (op.RegisterSize == RegisterSize.Int64) + { + nInt = context.SignExtend32(OperandType.I64, nInt); + } + + Operand dRes = context.BitwiseExclusiveOr(nIntOrLong, nInt); + + SetIntOrZR(context, op.Rd, dRes); + } + else /* if (op.Size == 1) */ + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (isFixed) + { + // BitConverter.Int64BitsToDouble(fpScaled) == Math.Pow(2d, op.FBits) + long fpScaled = 0x3FF0000000000000L + op.FBits * 0x10000000000000L; + + Operand fpScaledMask = X86GetScalar(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulsd, nRes, fpScaledMask); + } + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundsd, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar: true); + } + + Operand nIntOrLong = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt (Intrinsic.X86Cvtsd2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, nRes); + + long fpMaxVal = op.RegisterSize == RegisterSize.Int32 + ? 0x41E0000000000000L // 2147483648.0000000d (2147483648) + : 0x43E0000000000000L; // 9.2233720368547760E18d (9223372036854775808) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nLong = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, nRes); + + if (op.RegisterSize == RegisterSize.Int32) + { + nLong = context.ConvertI64ToI32(nLong); + } + + Operand dRes = context.BitwiseExclusiveOr(nIntOrLong, nLong); + + SetIntOrZR(context, op.Rd, dRes); + } + } + + private static void EmitSse41Fcvtu_Gp(ArmEmitterContext context, FPRoundingMode roundMode, bool isFixed) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if (op.Size == 0) + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (isFixed) + { + // BitConverter.Int32BitsToSingle(fpScaled) == MathF.Pow(2f, op.FBits) + int fpScaled = 0x3F800000 + op.FBits * 0x800000; + + Operand fpScaledMask = X86GetScalar(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulss, nRes, fpScaledMask); + } + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundss, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar: true); + } + + Operand zero = context.VectorZero(); + + Operand nCmp = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + int fpMaxVal = op.RegisterSize == RegisterSize.Int32 + ? 0x4F000000 // 2.14748365E9f (2147483648) + : 0x5F000000; // 9.223372E18f (9223372036854775808) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + Operand nIntOrLong = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt (Intrinsic.X86Cvtss2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtss2si, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Subss, nRes, fpMaxValMask); + + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + Operand nIntOrLong2 = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt (Intrinsic.X86Cvtss2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtss2si, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nInt = context.AddIntrinsicInt(Intrinsic.X86Cvtsi2si, nRes); + + if (op.RegisterSize == RegisterSize.Int64) + { + nInt = context.SignExtend32(OperandType.I64, nInt); + } + + Operand dRes = context.BitwiseExclusiveOr(nIntOrLong2, nInt); + dRes = context.Add(dRes, nIntOrLong); + + SetIntOrZR(context, op.Rd, dRes); + } + else /* if (op.Size == 1) */ + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (isFixed) + { + // BitConverter.Int64BitsToDouble(fpScaled) == Math.Pow(2d, op.FBits) + long fpScaled = 0x3FF0000000000000L + op.FBits * 0x10000000000000L; + + Operand fpScaledMask = X86GetScalar(context, fpScaled); + + nRes = context.AddIntrinsic(Intrinsic.X86Mulsd, nRes, fpScaledMask); + } + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundsd, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar: true); + } + + Operand zero = context.VectorZero(); + + Operand nCmp = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + long fpMaxVal = op.RegisterSize == RegisterSize.Int32 + ? 0x41E0000000000000L // 2147483648.0000000d (2147483648) + : 0x43E0000000000000L; // 9.2233720368547760E18d (9223372036854775808) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + Operand nIntOrLong = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt (Intrinsic.X86Cvtsd2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Subsd, nRes, fpMaxValMask); + + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + Operand nIntOrLong2 = op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt (Intrinsic.X86Cvtsd2si, nRes) + : context.AddIntrinsicLong(Intrinsic.X86Cvtsd2si, nRes); + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nLong = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, nRes); + + if (op.RegisterSize == RegisterSize.Int32) + { + nLong = context.ConvertI64ToI32(nLong); + } + + Operand dRes = context.BitwiseExclusiveOr(nIntOrLong2, nLong); + dRes = context.Add(dRes, nIntOrLong); + + SetIntOrZR(context, op.Rd, dRes); + } + } + + private static Operand EmitVectorLongExtract(ArmEmitterContext context, int reg, int index, int size) + { + OperandType type = size == 3 ? OperandType.I64 : OperandType.I32; + + return context.VectorExtract(type, GetVec(reg), index); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdCvt32.cs b/src/ARMeilleure/Instructions/InstEmitSimdCvt32.cs new file mode 100644 index 00000000..33ae83df --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdCvt32.cs @@ -0,0 +1,800 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using System.Reflection; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper32; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + private static int FlipVdBits(int vd, bool lowBit) + { + if (lowBit) + { + // Move the low bit to the top. + return ((vd & 0x1) << 4) | (vd >> 1); + } + else + { + // Move the high bit to the bottom. + return ((vd & 0xf) << 1) | (vd >> 4); + } + } + + private static Operand EmitSaturateFloatToInt(ArmEmitterContext context, Operand op1, bool unsigned) + { + MethodInfo info; + + if (op1.Type == OperandType.FP64) + { + info = unsigned + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToU32)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToS32)); + } + else + { + info = unsigned + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU32)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS32)); + } + + return context.Call(info, op1); + } + + public static void Vcvt_V(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + bool unsigned = (op.Opc & 1) != 0; + bool toInteger = (op.Opc & 2) != 0; + OperandType floatSize = (op.Size == 2) ? OperandType.FP32 : OperandType.FP64; + + if (toInteger) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, unsigned ? Intrinsic.Arm64FcvtzuV : Intrinsic.Arm64FcvtzsV); + } + else if (Optimizations.UseSse41) + { + EmitSse41ConvertVector32(context, FPRoundingMode.TowardsZero, !unsigned); + } + else + { + EmitVectorUnaryOpF32(context, (op1) => + { + return EmitSaturateFloatToInt(context, op1, unsigned); + }); + } + } + else + { + if (Optimizations.UseSse2) + { + EmitVectorUnaryOpSimd32(context, (n) => + { + if (unsigned) + { + Operand mask = X86GetAllElements(context, 0x47800000); + + Operand res = context.AddIntrinsic(Intrinsic.X86Psrld, n, Const(16)); + res = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, res); + res = context.AddIntrinsic(Intrinsic.X86Mulps, res, mask); + + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pslld, n, Const(16)); + res2 = context.AddIntrinsic(Intrinsic.X86Psrld, res2, Const(16)); + res2 = context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, res2); + + return context.AddIntrinsic(Intrinsic.X86Addps, res, res2); + } + else + { + return context.AddIntrinsic(Intrinsic.X86Cvtdq2ps, n); + } + }); + } + else + { + if (unsigned) + { + EmitVectorUnaryOpZx32(context, (op1) => EmitFPConvert(context, op1, floatSize, false)); + } + else + { + EmitVectorUnaryOpSx32(context, (op1) => EmitFPConvert(context, op1, floatSize, true)); + } + } + } + } + + public static void Vcvt_FD(ArmEmitterContext context) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + int vm = op.Vm; + int vd; + if (op.Size == 3) + { + vd = FlipVdBits(op.Vd, false); + // Double to single. + Operand fp = ExtractScalar(context, OperandType.FP64, vm); + + Operand res = context.ConvertToFP(OperandType.FP32, fp); + + InsertScalar(context, vd, res); + } + else + { + vd = FlipVdBits(op.Vd, true); + // Single to double. + Operand fp = ExtractScalar(context, OperandType.FP32, vm); + + Operand res = context.ConvertToFP(OperandType.FP64, fp); + + InsertScalar(context, vd, res); + } + } + + // VCVT (floating-point to integer, floating-point) | VCVT (integer to floating-point, floating-point). + public static void Vcvt_FI(ArmEmitterContext context) + { + OpCode32SimdCvtFI op = (OpCode32SimdCvtFI)context.CurrOp; + + bool toInteger = (op.Opc2 & 0b100) != 0; + + OperandType floatSize = op.RegisterSize == RegisterSize.Int64 ? OperandType.FP64 : OperandType.FP32; + + if (toInteger) + { + bool unsigned = (op.Opc2 & 1) == 0; + bool roundWithFpscr = op.Opc != 1; + + if (!roundWithFpscr && Optimizations.UseAdvSimd) + { + bool doubleSize = floatSize == OperandType.FP64; + + if (doubleSize) + { + Operand m = GetVecA32(op.Vm >> 1); + + Operand toConvert = InstEmitSimdHelper32Arm64.EmitExtractScalar(context, m, op.Vm, doubleSize); + + Intrinsic inst = (unsigned ? Intrinsic.Arm64FcvtzuGp : Intrinsic.Arm64FcvtzsGp) | Intrinsic.Arm64VDouble; + + Operand asInteger = context.AddIntrinsicInt(inst, toConvert); + + InsertScalar(context, op.Vd, asInteger); + } + else + { + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, unsigned ? Intrinsic.Arm64FcvtzuS : Intrinsic.Arm64FcvtzsS); + } + } + else if (!roundWithFpscr && Optimizations.UseSse41) + { + EmitSse41ConvertInt32(context, FPRoundingMode.TowardsZero, !unsigned); + } + else + { + Operand toConvert = ExtractScalar(context, floatSize, op.Vm); + + // TODO: Fast Path. + if (roundWithFpscr) + { + toConvert = EmitRoundByRMode(context, toConvert); + } + + // Round towards zero. + Operand asInteger = EmitSaturateFloatToInt(context, toConvert, unsigned); + + InsertScalar(context, op.Vd, asInteger); + } + } + else + { + bool unsigned = op.Opc == 0; + + Operand toConvert = ExtractScalar(context, OperandType.I32, op.Vm); + + Operand asFloat = EmitFPConvert(context, toConvert, floatSize, !unsigned); + + InsertScalar(context, op.Vd, asFloat); + } + } + + private static Operand EmitRoundMathCall(ArmEmitterContext context, MidpointRounding roundMode, Operand n) + { + IOpCode32Simd op = (IOpCode32Simd)context.CurrOp; + + string name = nameof(Math.Round); + + MethodInfo info = (op.Size & 1) == 0 + ? typeof(MathF).GetMethod(name, new Type[] { typeof(float), typeof(MidpointRounding) }) + : typeof(Math). GetMethod(name, new Type[] { typeof(double), typeof(MidpointRounding) }); + + return context.Call(info, n, Const((int)roundMode)); + } + + private static FPRoundingMode RMToRoundMode(int rm) + { + FPRoundingMode roundMode; + switch (rm) + { + case 0b00: + roundMode = FPRoundingMode.ToNearestAway; + break; + case 0b01: + roundMode = FPRoundingMode.ToNearest; + break; + case 0b10: + roundMode = FPRoundingMode.TowardsPlusInfinity; + break; + case 0b11: + roundMode = FPRoundingMode.TowardsMinusInfinity; + break; + default: + throw new ArgumentOutOfRangeException(nameof(rm)); + } + return roundMode; + } + + // VCVTA/M/N/P (floating-point). + public static void Vcvt_RM(ArmEmitterContext context) + { + OpCode32SimdCvtFI op = (OpCode32SimdCvtFI)context.CurrOp; // toInteger == true (opCode<18> == 1 => Opc2<2> == 1). + + OperandType floatSize = op.RegisterSize == RegisterSize.Int64 ? OperandType.FP64 : OperandType.FP32; + + bool unsigned = op.Opc == 0; + int rm = op.Opc2 & 3; + + Intrinsic inst; + + if (Optimizations.UseAdvSimd) + { + if (unsigned) + { + inst = rm switch { + 0b00 => Intrinsic.Arm64FcvtauS, + 0b01 => Intrinsic.Arm64FcvtnuS, + 0b10 => Intrinsic.Arm64FcvtpuS, + 0b11 => Intrinsic.Arm64FcvtmuS, + _ => throw new ArgumentOutOfRangeException(nameof(rm)) + }; + } + else + { + inst = rm switch { + 0b00 => Intrinsic.Arm64FcvtasS, + 0b01 => Intrinsic.Arm64FcvtnsS, + 0b10 => Intrinsic.Arm64FcvtpsS, + 0b11 => Intrinsic.Arm64FcvtmsS, + _ => throw new ArgumentOutOfRangeException(nameof(rm)) + }; + } + + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, inst); + } + else if (Optimizations.UseSse41) + { + EmitSse41ConvertInt32(context, RMToRoundMode(rm), !unsigned); + } + else + { + Operand toConvert = ExtractScalar(context, floatSize, op.Vm); + + switch (rm) + { + case 0b00: // Away + toConvert = EmitRoundMathCall(context, MidpointRounding.AwayFromZero, toConvert); + break; + case 0b01: // Nearest + toConvert = EmitRoundMathCall(context, MidpointRounding.ToEven, toConvert); + break; + case 0b10: // Towards positive infinity + toConvert = EmitUnaryMathCall(context, nameof(Math.Ceiling), toConvert); + break; + case 0b11: // Towards negative infinity + toConvert = EmitUnaryMathCall(context, nameof(Math.Floor), toConvert); + break; + } + + Operand asInteger = EmitSaturateFloatToInt(context, toConvert, unsigned); + + InsertScalar(context, op.Vd, asInteger); + } + } + + public static void Vcvt_TB(ArmEmitterContext context) + { + OpCode32SimdCvtTB op = (OpCode32SimdCvtTB)context.CurrOp; + + if (Optimizations.UseF16c) + { + Debug.Assert(!Optimizations.ForceLegacySse); + + if (op.Op) + { + Operand res = ExtractScalar(context, op.Size == 1 ? OperandType.FP64 : OperandType.FP32, op.Vm); + if (op.Size == 1) + { + res = context.AddIntrinsic(Intrinsic.X86Cvtsd2ss, context.VectorZero(), res); + } + res = context.AddIntrinsic(Intrinsic.X86Vcvtps2ph, res, Const(X86GetRoundControl(FPRoundingMode.ToNearest))); + res = context.VectorExtract16(res, 0); + InsertScalar16(context, op.Vd, op.T, res); + } + else + { + Operand res = context.VectorCreateScalar(ExtractScalar16(context, op.Vm, op.T)); + res = context.AddIntrinsic(Intrinsic.X86Vcvtph2ps, res); + if (op.Size == 1) + { + res = context.AddIntrinsic(Intrinsic.X86Cvtss2sd, context.VectorZero(), res); + } + res = context.VectorExtract(op.Size == 1 ? OperandType.I64 : OperandType.I32, res, 0); + InsertScalar(context, op.Vd, res); + } + } + else + { + if (op.Op) + { + // Convert to half. + + Operand src = ExtractScalar(context, op.Size == 1 ? OperandType.FP64 : OperandType.FP32, op.Vm); + + MethodInfo method = op.Size == 1 + ? typeof(SoftFloat64_16).GetMethod(nameof(SoftFloat64_16.FPConvert)) + : typeof(SoftFloat32_16).GetMethod(nameof(SoftFloat32_16.FPConvert)); + + context.ExitArmFpMode(); + context.StoreToContext(); + Operand res = context.Call(method, src); + context.LoadFromContext(); + context.EnterArmFpMode(); + + InsertScalar16(context, op.Vd, op.T, res); + } + else + { + // Convert from half. + + Operand src = ExtractScalar16(context, op.Vm, op.T); + + MethodInfo method = op.Size == 1 + ? typeof(SoftFloat16_64).GetMethod(nameof(SoftFloat16_64.FPConvert)) + : typeof(SoftFloat16_32).GetMethod(nameof(SoftFloat16_32.FPConvert)); + + context.ExitArmFpMode(); + context.StoreToContext(); + Operand res = context.Call(method, src); + context.LoadFromContext(); + context.EnterArmFpMode(); + + InsertScalar(context, op.Vd, res); + } + } + } + + // VRINTA/M/N/P (floating-point). + public static void Vrint_RM(ArmEmitterContext context) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + OperandType floatSize = op.RegisterSize == RegisterSize.Int64 ? OperandType.FP64 : OperandType.FP32; + + int rm = op.Opc2 & 3; + + if (Optimizations.UseAdvSimd) + { + Intrinsic inst = rm switch { + 0b00 => Intrinsic.Arm64FrintaS, + 0b01 => Intrinsic.Arm64FrintnS, + 0b10 => Intrinsic.Arm64FrintpS, + 0b11 => Intrinsic.Arm64FrintmS, + _ => throw new ArgumentOutOfRangeException(nameof(rm)) + }; + + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, inst); + } + else if (Optimizations.UseSse41) + { + EmitScalarUnaryOpSimd32(context, (m) => + { + FPRoundingMode roundMode = RMToRoundMode(rm); + + if (roundMode != FPRoundingMode.ToNearestAway) + { + Intrinsic inst = (op.Size & 1) == 0 ? Intrinsic.X86Roundss : Intrinsic.X86Roundsd; + return context.AddIntrinsic(inst, m, Const(X86GetRoundControl(roundMode))); + } + else + { + return EmitSse41RoundToNearestWithTiesToAwayOpF(context, m, scalar: true); + } + }); + } + else + { + Operand toConvert = ExtractScalar(context, floatSize, op.Vm); + + switch (rm) + { + case 0b00: // Away + toConvert = EmitRoundMathCall(context, MidpointRounding.AwayFromZero, toConvert); + break; + case 0b01: // Nearest + toConvert = EmitRoundMathCall(context, MidpointRounding.ToEven, toConvert); + break; + case 0b10: // Towards positive infinity + toConvert = EmitUnaryMathCall(context, nameof(Math.Ceiling), toConvert); + break; + case 0b11: // Towards negative infinity + toConvert = EmitUnaryMathCall(context, nameof(Math.Floor), toConvert); + break; + } + + InsertScalar(context, op.Vd, toConvert); + } + } + + // VRINTA (vector). + public static void Vrinta_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FrintaS); + } + else + { + EmitVectorUnaryOpF32(context, (m) => EmitRoundMathCall(context, MidpointRounding.AwayFromZero, m)); + } + } + + // VRINTM (vector). + public static void Vrintm_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FrintmS); + } + else if (Optimizations.UseSse2) + { + EmitVectorUnaryOpSimd32(context, (m) => + { + return context.AddIntrinsic(Intrinsic.X86Roundps, m, Const(X86GetRoundControl(FPRoundingMode.TowardsMinusInfinity))); + }); + } + else + { + EmitVectorUnaryOpF32(context, (m) => EmitUnaryMathCall(context, nameof(Math.Floor), m)); + } + } + + // VRINTN (vector). + public static void Vrintn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FrintnS); + } + else if (Optimizations.UseSse2) + { + EmitVectorUnaryOpSimd32(context, (m) => + { + return context.AddIntrinsic(Intrinsic.X86Roundps, m, Const(X86GetRoundControl(FPRoundingMode.ToNearest))); + }); + } + else + { + EmitVectorUnaryOpF32(context, (m) => EmitRoundMathCall(context, MidpointRounding.ToEven, m)); + } + } + + // VRINTP (vector). + public static void Vrintp_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorUnaryOpF32(context, Intrinsic.Arm64FrintpS); + } + else if (Optimizations.UseSse2) + { + EmitVectorUnaryOpSimd32(context, (m) => + { + return context.AddIntrinsic(Intrinsic.X86Roundps, m, Const(X86GetRoundControl(FPRoundingMode.TowardsPlusInfinity))); + }); + } + else + { + EmitVectorUnaryOpF32(context, (m) => EmitUnaryMathCall(context, nameof(Math.Ceiling), m)); + } + } + + // VRINTZ (floating-point). + public static void Vrint_Z(ArmEmitterContext context) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FrintzS); + } + else if (Optimizations.UseSse2) + { + EmitScalarUnaryOpSimd32(context, (m) => + { + Intrinsic inst = (op.Size & 1) == 0 ? Intrinsic.X86Roundss : Intrinsic.X86Roundsd; + return context.AddIntrinsic(inst, m, Const(X86GetRoundControl(FPRoundingMode.TowardsZero))); + }); + } + else + { + EmitScalarUnaryOpF32(context, (op1) => EmitUnaryMathCall(context, nameof(Math.Truncate), op1)); + } + } + + // VRINTX (floating-point). + public static void Vrintx_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FrintxS); + } + else + { + EmitScalarUnaryOpF32(context, (op1) => + { + return EmitRoundByRMode(context, op1); + }); + } + } + + private static Operand EmitFPConvert(ArmEmitterContext context, Operand value, OperandType type, bool signed) + { + Debug.Assert(value.Type == OperandType.I32 || value.Type == OperandType.I64); + + if (signed) + { + return context.ConvertToFP(type, value); + } + else + { + return context.ConvertToFPUI(type, value); + } + } + + private static void EmitSse41ConvertInt32(ArmEmitterContext context, FPRoundingMode roundMode, bool signed) + { + // A port of the similar round function in InstEmitSimdCvt. + OpCode32SimdCvtFI op = (OpCode32SimdCvtFI)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + int shift = doubleSize ? 1 : 2; + Operand n = GetVecA32(op.Vm >> shift); + n = EmitSwapScalar(context, n, op.Vm, doubleSize); + + if (!doubleSize) + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundss, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar: true); + } + + Operand zero = context.VectorZero(); + + Operand nCmp; + Operand nIntOrLong2 = default; + + if (!signed) + { + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + } + + int fpMaxVal = 0x4F000000; // 2.14748365E9f (2147483648) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + Operand nIntOrLong = context.AddIntrinsicInt(Intrinsic.X86Cvtss2si, nRes); + + if (!signed) + { + nRes = context.AddIntrinsic(Intrinsic.X86Subss, nRes, fpMaxValMask); + + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + nIntOrLong2 = context.AddIntrinsicInt(Intrinsic.X86Cvtss2si, nRes); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpss, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nInt = context.AddIntrinsicInt(Intrinsic.X86Cvtsi2si, nRes); + + Operand dRes; + if (signed) + { + dRes = context.BitwiseExclusiveOr(nIntOrLong, nInt); + } + else + { + dRes = context.BitwiseExclusiveOr(nIntOrLong2, nInt); + dRes = context.Add(dRes, nIntOrLong); + } + + InsertScalar(context, op.Vd, dRes); + } + else + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + if (roundMode != FPRoundingMode.ToNearestAway) + { + nRes = context.AddIntrinsic(Intrinsic.X86Roundsd, nRes, Const(X86GetRoundControl(roundMode))); + } + else + { + nRes = EmitSse41RoundToNearestWithTiesToAwayOpF(context, nRes, scalar: true); + } + + Operand zero = context.VectorZero(); + + Operand nCmp; + Operand nIntOrLong2 = default; + + if (!signed) + { + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + } + + long fpMaxVal = 0x41E0000000000000L; // 2147483648.0000000d (2147483648) + + Operand fpMaxValMask = X86GetScalar(context, fpMaxVal); + + Operand nIntOrLong = context.AddIntrinsicInt(Intrinsic.X86Cvtsd2si, nRes); + + if (!signed) + { + nRes = context.AddIntrinsic(Intrinsic.X86Subsd, nRes, fpMaxValMask); + + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + nIntOrLong2 = context.AddIntrinsicInt(Intrinsic.X86Cvtsd2si, nRes); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpsd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + Operand nLong = context.AddIntrinsicLong(Intrinsic.X86Cvtsi2si, nRes); + nLong = context.ConvertI64ToI32(nLong); + + Operand dRes; + if (signed) + { + dRes = context.BitwiseExclusiveOr(nIntOrLong, nLong); + } + else + { + dRes = context.BitwiseExclusiveOr(nIntOrLong2, nLong); + dRes = context.Add(dRes, nIntOrLong); + } + + InsertScalar(context, op.Vd, dRes); + } + } + + private static void EmitSse41ConvertVector32(ArmEmitterContext context, FPRoundingMode roundMode, bool signed) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + EmitVectorUnaryOpSimd32(context, (n) => + { + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + nRes = context.AddIntrinsic(Intrinsic.X86Roundps, nRes, Const(X86GetRoundControl(roundMode))); + + Operand zero = context.VectorZero(); + Operand nCmp; + if (!signed) + { + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + } + + Operand fpMaxValMask = X86GetAllElements(context, 0x4F000000); // 2.14748365E9f (2147483648) + + Operand nInt = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRes); + Operand nInt2 = default; + + if (!signed) + { + nRes = context.AddIntrinsic(Intrinsic.X86Subps, nRes, fpMaxValMask); + + nCmp = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + nInt2 = context.AddIntrinsic(Intrinsic.X86Cvtps2dq, nRes); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Cmpps, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + if (signed) + { + return context.AddIntrinsic(Intrinsic.X86Pxor, nInt, nRes); + } + else + { + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nInt2, nRes); + return context.AddIntrinsic(Intrinsic.X86Paddd, dRes, nInt); + } + } + else /* if (sizeF == 1) */ + { + Operand nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, n, n, Const((int)CmpCondition.OrderedQ)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, n); + + nRes = context.AddIntrinsic(Intrinsic.X86Roundpd, nRes, Const(X86GetRoundControl(roundMode))); + + Operand zero = context.VectorZero(); + Operand nCmp; + if (!signed) + { + nCmp = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + } + + Operand fpMaxValMask = X86GetAllElements(context, 0x43E0000000000000L); // 9.2233720368547760E18d (9223372036854775808) + + Operand nLong = InstEmit.EmitSse2CvtDoubleToInt64OpF(context, nRes, false); + Operand nLong2 = default; + + if (!signed) + { + nRes = context.AddIntrinsic(Intrinsic.X86Subpd, nRes, fpMaxValMask); + + nCmp = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, zero, Const((int)CmpCondition.NotLessThanOrEqual)); + nRes = context.AddIntrinsic(Intrinsic.X86Pand, nRes, nCmp); + + nLong2 = InstEmit.EmitSse2CvtDoubleToInt64OpF(context, nRes, false); + } + + nRes = context.AddIntrinsic(Intrinsic.X86Cmppd, nRes, fpMaxValMask, Const((int)CmpCondition.NotLessThan)); + + if (signed) + { + return context.AddIntrinsic(Intrinsic.X86Pxor, nLong, nRes); + } + else + { + Operand dRes = context.AddIntrinsic(Intrinsic.X86Pxor, nLong2, nRes); + return context.AddIntrinsic(Intrinsic.X86Paddq, dRes, nLong); + } + } + }); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdHash.cs b/src/ARMeilleure/Instructions/InstEmitSimdHash.cs new file mode 100644 index 00000000..4fb048ee --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdHash.cs @@ -0,0 +1,147 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { +#region "Sha1" + public static void Sha1c_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + + Operand ne = context.VectorExtract(OperandType.I32, GetVec(op.Rn), 0); + + Operand m = GetVec(op.Rm); + + Operand res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashChoose)), d, ne, m); + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sha1h_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand ne = context.VectorExtract(OperandType.I32, GetVec(op.Rn), 0); + + Operand res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.FixedRotate)), ne); + + context.Copy(GetVec(op.Rd), context.VectorCreateScalar(res)); + } + + public static void Sha1m_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + + Operand ne = context.VectorExtract(OperandType.I32, GetVec(op.Rn), 0); + + Operand m = GetVec(op.Rm); + + Operand res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashMajority)), d, ne, m); + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sha1p_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + + Operand ne = context.VectorExtract(OperandType.I32, GetVec(op.Rn), 0); + + Operand m = GetVec(op.Rm); + + Operand res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashParity)), d, ne, m); + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sha1su0_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha1SchedulePart1)), d, n, m); + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sha1su1_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Operand res = context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha1SchedulePart2)), d, n); + + context.Copy(GetVec(op.Rd), res); + } +#endregion + +#region "Sha256" + public static void Sha256h_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = InstEmitSimdHashHelper.EmitSha256h(context, d, n, m, part2: false); + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sha256h2_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = InstEmitSimdHashHelper.EmitSha256h(context, n, d, m, part2: true); + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sha256su0_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Operand res = InstEmitSimdHashHelper.EmitSha256su0(context, d, n); + + context.Copy(GetVec(op.Rd), res); + } + + public static void Sha256su1_V(ArmEmitterContext context) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = InstEmitSimdHashHelper.EmitSha256su1(context, d, n, m); + + context.Copy(GetVec(op.Rd), res); + } +#endregion + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdHash32.cs b/src/ARMeilleure/Instructions/InstEmitSimdHash32.cs new file mode 100644 index 00000000..51334608 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdHash32.cs @@ -0,0 +1,64 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { +#region "Sha256" + public static void Sha256h_V(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand d = GetVecA32(op.Qd); + Operand n = GetVecA32(op.Qn); + Operand m = GetVecA32(op.Qm); + + Operand res = InstEmitSimdHashHelper.EmitSha256h(context, d, n, m, part2: false); + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Sha256h2_V(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand d = GetVecA32(op.Qd); + Operand n = GetVecA32(op.Qn); + Operand m = GetVecA32(op.Qm); + + Operand res = InstEmitSimdHashHelper.EmitSha256h(context, n, d, m, part2: true); + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Sha256su0_V(ArmEmitterContext context) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand d = GetVecA32(op.Qd); + Operand m = GetVecA32(op.Qm); + + Operand res = InstEmitSimdHashHelper.EmitSha256su0(context, d, m); + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Sha256su1_V(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand d = GetVecA32(op.Qd); + Operand n = GetVecA32(op.Qn); + Operand m = GetVecA32(op.Qm); + + Operand res = InstEmitSimdHashHelper.EmitSha256su1(context, d, n, m); + + context.Copy(GetVecA32(op.Qd), res); + } +#endregion + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdHashHelper.cs b/src/ARMeilleure/Instructions/InstEmitSimdHashHelper.cs new file mode 100644 index 00000000..23e4948d --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdHashHelper.cs @@ -0,0 +1,56 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; + +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitSimdHashHelper + { + public static Operand EmitSha256h(ArmEmitterContext context, Operand x, Operand y, Operand w, bool part2) + { + if (Optimizations.UseSha) + { + Operand src1 = context.AddIntrinsic(Intrinsic.X86Shufps, y, x, Const(0xbb)); + Operand src2 = context.AddIntrinsic(Intrinsic.X86Shufps, y, x, Const(0x11)); + Operand w2 = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, w, w); + + Operand round2 = context.AddIntrinsic(Intrinsic.X86Sha256Rnds2, src1, src2, w); + Operand round4 = context.AddIntrinsic(Intrinsic.X86Sha256Rnds2, src2, round2, w2); + + Operand res = context.AddIntrinsic(Intrinsic.X86Shufps, round4, round2, Const(part2 ? 0x11 : 0xbb)); + + return res; + } + + String method = part2 ? nameof(SoftFallback.HashUpper) : nameof(SoftFallback.HashLower); + return context.Call(typeof(SoftFallback).GetMethod(method), x, y, w); + } + + public static Operand EmitSha256su0(ArmEmitterContext context, Operand x, Operand y) + { + if (Optimizations.UseSha) + { + return context.AddIntrinsic(Intrinsic.X86Sha256Msg1, x, y); + } + + return context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha256SchedulePart1)), x, y); + } + + public static Operand EmitSha256su1(ArmEmitterContext context, Operand x, Operand y, Operand z) + { + if (Optimizations.UseSha && Optimizations.UseSsse3) + { + Operand extr = context.AddIntrinsic(Intrinsic.X86Palignr, z, y, Const(4)); + Operand tmp = context.AddIntrinsic(Intrinsic.X86Paddd, extr, x); + + Operand res = context.AddIntrinsic(Intrinsic.X86Sha256Msg2, tmp, z); + + return res; + } + + return context.Call(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha256SchedulePart2)), x, y, z); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Instructions/InstEmitSimdHelper.cs b/src/ARMeilleure/Instructions/InstEmitSimdHelper.cs new file mode 100644 index 00000000..c44c9b4d --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdHelper.cs @@ -0,0 +1,2088 @@ +using ARMeilleure.CodeGen.X86; +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using System.Reflection; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + using Func1I = Func; + using Func2I = Func; + using Func3I = Func; + + static class InstEmitSimdHelper + { +#region "Masks" + public static readonly long[] EvenMasks = new long[] + { + 14L << 56 | 12L << 48 | 10L << 40 | 08L << 32 | 06L << 24 | 04L << 16 | 02L << 8 | 00L << 0, // B + 13L << 56 | 12L << 48 | 09L << 40 | 08L << 32 | 05L << 24 | 04L << 16 | 01L << 8 | 00L << 0, // H + 11L << 56 | 10L << 48 | 09L << 40 | 08L << 32 | 03L << 24 | 02L << 16 | 01L << 8 | 00L << 0 // S + }; + + public static readonly long[] OddMasks = new long[] + { + 15L << 56 | 13L << 48 | 11L << 40 | 09L << 32 | 07L << 24 | 05L << 16 | 03L << 8 | 01L << 0, // B + 15L << 56 | 14L << 48 | 11L << 40 | 10L << 32 | 07L << 24 | 06L << 16 | 03L << 8 | 02L << 0, // H + 15L << 56 | 14L << 48 | 13L << 40 | 12L << 32 | 07L << 24 | 06L << 16 | 05L << 8 | 04L << 0 // S + }; + + public static readonly long ZeroMask = 128L << 56 | 128L << 48 | 128L << 40 | 128L << 32 | 128L << 24 | 128L << 16 | 128L << 8 | 128L << 0; + + public static ulong X86GetGf2p8LogicalShiftLeft(int shift) + { + ulong identity = (0b00000001UL << 56) | (0b00000010UL << 48) | (0b00000100UL << 40) | (0b00001000UL << 32) | + (0b00010000UL << 24) | (0b00100000UL << 16) | (0b01000000UL << 8) | (0b10000000UL << 0); + + return shift >= 0 ? identity >> (shift * 8) : identity << (-shift * 8); + } +#endregion + +#region "X86 SSE Intrinsics" + public static readonly Intrinsic[] X86PaddInstruction = new Intrinsic[] + { + Intrinsic.X86Paddb, + Intrinsic.X86Paddw, + Intrinsic.X86Paddd, + Intrinsic.X86Paddq + }; + + public static readonly Intrinsic[] X86PcmpeqInstruction = new Intrinsic[] + { + Intrinsic.X86Pcmpeqb, + Intrinsic.X86Pcmpeqw, + Intrinsic.X86Pcmpeqd, + Intrinsic.X86Pcmpeqq + }; + + public static readonly Intrinsic[] X86PcmpgtInstruction = new Intrinsic[] + { + Intrinsic.X86Pcmpgtb, + Intrinsic.X86Pcmpgtw, + Intrinsic.X86Pcmpgtd, + Intrinsic.X86Pcmpgtq + }; + + public static readonly Intrinsic[] X86PmaxsInstruction = new Intrinsic[] + { + Intrinsic.X86Pmaxsb, + Intrinsic.X86Pmaxsw, + Intrinsic.X86Pmaxsd + }; + + public static readonly Intrinsic[] X86PmaxuInstruction = new Intrinsic[] + { + Intrinsic.X86Pmaxub, + Intrinsic.X86Pmaxuw, + Intrinsic.X86Pmaxud + }; + + public static readonly Intrinsic[] X86PminsInstruction = new Intrinsic[] + { + Intrinsic.X86Pminsb, + Intrinsic.X86Pminsw, + Intrinsic.X86Pminsd + }; + + public static readonly Intrinsic[] X86PminuInstruction = new Intrinsic[] + { + Intrinsic.X86Pminub, + Intrinsic.X86Pminuw, + Intrinsic.X86Pminud + }; + + public static readonly Intrinsic[] X86PmovsxInstruction = new Intrinsic[] + { + Intrinsic.X86Pmovsxbw, + Intrinsic.X86Pmovsxwd, + Intrinsic.X86Pmovsxdq + }; + + public static readonly Intrinsic[] X86PmovzxInstruction = new Intrinsic[] + { + Intrinsic.X86Pmovzxbw, + Intrinsic.X86Pmovzxwd, + Intrinsic.X86Pmovzxdq + }; + + public static readonly Intrinsic[] X86PsllInstruction = new Intrinsic[] + { + 0, + Intrinsic.X86Psllw, + Intrinsic.X86Pslld, + Intrinsic.X86Psllq + }; + + public static readonly Intrinsic[] X86PsraInstruction = new Intrinsic[] + { + 0, + Intrinsic.X86Psraw, + Intrinsic.X86Psrad + }; + + public static readonly Intrinsic[] X86PsrlInstruction = new Intrinsic[] + { + 0, + Intrinsic.X86Psrlw, + Intrinsic.X86Psrld, + Intrinsic.X86Psrlq + }; + + public static readonly Intrinsic[] X86PsubInstruction = new Intrinsic[] + { + Intrinsic.X86Psubb, + Intrinsic.X86Psubw, + Intrinsic.X86Psubd, + Intrinsic.X86Psubq + }; + + public static readonly Intrinsic[] X86PunpckhInstruction = new Intrinsic[] + { + Intrinsic.X86Punpckhbw, + Intrinsic.X86Punpckhwd, + Intrinsic.X86Punpckhdq, + Intrinsic.X86Punpckhqdq + }; + + public static readonly Intrinsic[] X86PunpcklInstruction = new Intrinsic[] + { + Intrinsic.X86Punpcklbw, + Intrinsic.X86Punpcklwd, + Intrinsic.X86Punpckldq, + Intrinsic.X86Punpcklqdq + }; +#endregion + + public static void EnterArmFpMode(EmitterContext context, Func getFpFlag) + { + if (Optimizations.UseSse2) + { + Operand mxcsr = context.AddIntrinsicInt(Intrinsic.X86Stmxcsr); + + Operand fzTrue = getFpFlag(FPState.FzFlag); + Operand r0True = getFpFlag(FPState.RMode0Flag); + Operand r1True = getFpFlag(FPState.RMode1Flag); + + mxcsr = context.BitwiseAnd(mxcsr, Const(~(int)(Mxcsr.Ftz | Mxcsr.Daz | Mxcsr.Rhi | Mxcsr.Rlo))); + + mxcsr = context.BitwiseOr(mxcsr, context.ConditionalSelect(fzTrue, Const((int)(Mxcsr.Ftz | Mxcsr.Daz | Mxcsr.Um | Mxcsr.Dm)), Const(0))); + + // X86 round modes in order: nearest, negative, positive, zero + // ARM round modes in order: nearest, positive, negative, zero + // Read the bits backwards to correct this. + + mxcsr = context.BitwiseOr(mxcsr, context.ConditionalSelect(r0True, Const((int)Mxcsr.Rhi), Const(0))); + mxcsr = context.BitwiseOr(mxcsr, context.ConditionalSelect(r1True, Const((int)Mxcsr.Rlo), Const(0))); + + context.AddIntrinsicNoRet(Intrinsic.X86Ldmxcsr, mxcsr); + } + else if (Optimizations.UseAdvSimd) + { + Operand fpcr = context.AddIntrinsicInt(Intrinsic.Arm64MrsFpcr); + + Operand fzTrue = getFpFlag(FPState.FzFlag); + Operand r0True = getFpFlag(FPState.RMode0Flag); + Operand r1True = getFpFlag(FPState.RMode1Flag); + + fpcr = context.BitwiseAnd(fpcr, Const(~(int)(FPCR.Fz | FPCR.RMode0 | FPCR.RMode1))); + + fpcr = context.BitwiseOr(fpcr, context.ConditionalSelect(fzTrue, Const((int)FPCR.Fz), Const(0))); + fpcr = context.BitwiseOr(fpcr, context.ConditionalSelect(r0True, Const((int)FPCR.RMode0), Const(0))); + fpcr = context.BitwiseOr(fpcr, context.ConditionalSelect(r1True, Const((int)FPCR.RMode1), Const(0))); + + context.AddIntrinsicNoRet(Intrinsic.Arm64MsrFpcr, fpcr); + + // TODO: Restore FPSR + } + } + + public static void ExitArmFpMode(EmitterContext context, Action setFpFlag) + { + if (Optimizations.UseSse2) + { + Operand mxcsr = context.AddIntrinsicInt(Intrinsic.X86Stmxcsr); + + // Unset round mode (to nearest) and ftz. + mxcsr = context.BitwiseAnd(mxcsr, Const(~(int)(Mxcsr.Ftz | Mxcsr.Daz | Mxcsr.Rhi | Mxcsr.Rlo))); + + context.AddIntrinsicNoRet(Intrinsic.X86Ldmxcsr, mxcsr); + + // Status flags would be stored here if they were used. + } + else if (Optimizations.UseAdvSimd) + { + Operand fpcr = context.AddIntrinsicInt(Intrinsic.Arm64MrsFpcr); + + // Unset round mode (to nearest) and fz. + fpcr = context.BitwiseAnd(fpcr, Const(~(int)(FPCR.Fz | FPCR.RMode0 | FPCR.RMode1))); + + context.AddIntrinsicNoRet(Intrinsic.Arm64MsrFpcr, fpcr); + + // TODO: Store FPSR + } + } + + public static int GetImmShl(OpCodeSimdShImm op) + { + return op.Imm - (8 << op.Size); + } + + public static int GetImmShr(OpCodeSimdShImm op) + { + return (8 << (op.Size + 1)) - op.Imm; + } + + public static Operand X86GetScalar(ArmEmitterContext context, float value) + { + return X86GetScalar(context, BitConverter.SingleToInt32Bits(value)); + } + + public static Operand X86GetScalar(ArmEmitterContext context, double value) + { + return X86GetScalar(context, BitConverter.DoubleToInt64Bits(value)); + } + + public static Operand X86GetScalar(ArmEmitterContext context, int value) + { + return context.VectorCreateScalar(Const(value)); + } + + public static Operand X86GetScalar(ArmEmitterContext context, long value) + { + return context.VectorCreateScalar(Const(value)); + } + + public static Operand X86GetAllElements(ArmEmitterContext context, float value) + { + return X86GetAllElements(context, BitConverter.SingleToInt32Bits(value)); + } + + public static Operand X86GetAllElements(ArmEmitterContext context, double value) + { + return X86GetAllElements(context, BitConverter.DoubleToInt64Bits(value)); + } + + public static Operand X86GetAllElements(ArmEmitterContext context, short value) + { + ulong value1 = (ushort)value; + ulong value2 = value1 << 16 | value1; + ulong value4 = value2 << 32 | value2; + + return X86GetAllElements(context, (long)value4); + } + + public static Operand X86GetAllElements(ArmEmitterContext context, int value) + { + Operand vector = context.VectorCreateScalar(Const(value)); + + vector = context.AddIntrinsic(Intrinsic.X86Shufps, vector, vector, Const(0)); + + return vector; + } + + public static Operand X86GetAllElements(ArmEmitterContext context, long value) + { + Operand vector = context.VectorCreateScalar(Const(value)); + + vector = context.AddIntrinsic(Intrinsic.X86Movlhps, vector, vector); + + return vector; + } + + public static Operand X86GetElements(ArmEmitterContext context, long e1, long e0) + { + return X86GetElements(context, (ulong)e1, (ulong)e0); + } + + public static Operand X86GetElements(ArmEmitterContext context, ulong e1, ulong e0) + { + Operand vector0 = context.VectorCreateScalar(Const(e0)); + Operand vector1 = context.VectorCreateScalar(Const(e1)); + + return context.AddIntrinsic(Intrinsic.X86Punpcklqdq, vector0, vector1); + } + + public static int X86GetRoundControl(FPRoundingMode roundMode) + { + switch (roundMode) + { + case FPRoundingMode.ToNearest: return 8 | 0; // even + case FPRoundingMode.TowardsPlusInfinity: return 8 | 2; + case FPRoundingMode.TowardsMinusInfinity: return 8 | 1; + case FPRoundingMode.TowardsZero: return 8 | 3; + } + + throw new ArgumentException($"Invalid rounding mode \"{roundMode}\"."); + } + + public static Operand EmitSse41RoundToNearestWithTiesToAwayOpF(ArmEmitterContext context, Operand n, bool scalar) + { + Debug.Assert(n.Type == OperandType.V128); + + Operand nCopy = context.Copy(n); + + Operand rC = Const(X86GetRoundControl(FPRoundingMode.TowardsZero)); + + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + if ((op.Size & 1) == 0) + { + Operand signMask = scalar ? X86GetScalar(context, int.MinValue) : X86GetAllElements(context, int.MinValue); + signMask = context.AddIntrinsic(Intrinsic.X86Pand, signMask, nCopy); + + // 0x3EFFFFFF == BitConverter.SingleToInt32Bits(0.5f) - 1 + Operand valueMask = scalar ? X86GetScalar(context, 0x3EFFFFFF) : X86GetAllElements(context, 0x3EFFFFFF); + valueMask = context.AddIntrinsic(Intrinsic.X86Por, valueMask, signMask); + + nCopy = context.AddIntrinsic(scalar ? Intrinsic.X86Addss : Intrinsic.X86Addps, nCopy, valueMask); + + nCopy = context.AddIntrinsic(scalar ? Intrinsic.X86Roundss : Intrinsic.X86Roundps, nCopy, rC); + } + else + { + Operand signMask = scalar ? X86GetScalar(context, long.MinValue) : X86GetAllElements(context, long.MinValue); + signMask = context.AddIntrinsic(Intrinsic.X86Pand, signMask, nCopy); + + // 0x3FDFFFFFFFFFFFFFL == BitConverter.DoubleToInt64Bits(0.5d) - 1L + Operand valueMask = scalar ? X86GetScalar(context, 0x3FDFFFFFFFFFFFFFL) : X86GetAllElements(context, 0x3FDFFFFFFFFFFFFFL); + valueMask = context.AddIntrinsic(Intrinsic.X86Por, valueMask, signMask); + + nCopy = context.AddIntrinsic(scalar ? Intrinsic.X86Addsd : Intrinsic.X86Addpd, nCopy, valueMask); + + nCopy = context.AddIntrinsic(scalar ? Intrinsic.X86Roundsd : Intrinsic.X86Roundpd, nCopy, rC); + } + + return nCopy; + } + + public static Operand EmitCountSetBits8(ArmEmitterContext context, Operand op) // "size" is 8 (SIMD&FP Inst.). + { + Debug.Assert(op.Type == OperandType.I32 || op.Type == OperandType.I64); + + Operand op0 = context.Subtract(op, context.BitwiseAnd(context.ShiftRightUI(op, Const(1)), Const(op.Type, 0x55L))); + + Operand c1 = Const(op.Type, 0x33L); + Operand op1 = context.Add(context.BitwiseAnd(context.ShiftRightUI(op0, Const(2)), c1), context.BitwiseAnd(op0, c1)); + + return context.BitwiseAnd(context.Add(op1, context.ShiftRightUI(op1, Const(4))), Const(op.Type, 0x0fL)); + } + + public static void EmitScalarUnaryOpF(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + + Operand res = context.AddIntrinsic(inst, n); + + if ((op.Size & 1) != 0) + { + res = context.VectorZeroUpper64(res); + } + else + { + res = context.VectorZeroUpper96(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitScalarBinaryOpF(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + + Operand res = context.AddIntrinsic(inst, n, m); + + if ((op.Size & 1) != 0) + { + res = context.VectorZeroUpper64(res); + } + else + { + res = context.VectorZeroUpper96(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorUnaryOpF(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + + Operand res = context.AddIntrinsic(inst, n); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorBinaryOpF(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + + Operand res = context.AddIntrinsic(inst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static Operand EmitUnaryMathCall(ArmEmitterContext context, string name, Operand n) + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + MethodInfo info = (op.Size & 1) == 0 + ? typeof(MathF).GetMethod(name, new Type[] { typeof(float) }) + : typeof(Math). GetMethod(name, new Type[] { typeof(double) }); + + return context.Call(info, n); + } + + public static Operand EmitRoundMathCall(ArmEmitterContext context, MidpointRounding roundMode, Operand n) + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + string name = nameof(Math.Round); + + MethodInfo info = (op.Size & 1) == 0 + ? typeof(MathF).GetMethod(name, new Type[] { typeof(float), typeof(MidpointRounding) }) + : typeof(Math). GetMethod(name, new Type[] { typeof(double), typeof(MidpointRounding) }); + + return context.Call(info, n, Const((int)roundMode)); + } + + public static Operand EmitGetRoundingMode(ArmEmitterContext context) + { + Operand rMode = context.ShiftLeft(GetFpFlag(FPState.RMode1Flag), Const(1)); + rMode = context.BitwiseOr(rMode, GetFpFlag(FPState.RMode0Flag)); + + return rMode; + } + + public static Operand EmitRoundByRMode(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.FP32 || op.Type == OperandType.FP64); + + Operand lbl1 = Label(); + Operand lbl2 = Label(); + Operand lbl3 = Label(); + Operand lblEnd = Label(); + + Operand rN = Const((int)FPRoundingMode.ToNearest); + Operand rP = Const((int)FPRoundingMode.TowardsPlusInfinity); + Operand rM = Const((int)FPRoundingMode.TowardsMinusInfinity); + + Operand res = context.AllocateLocal(op.Type); + + Operand rMode = EmitGetRoundingMode(context); + + context.BranchIf(lbl1, rMode, rN, Comparison.NotEqual); + context.Copy(res, EmitRoundMathCall(context, MidpointRounding.ToEven, op)); + context.Branch(lblEnd); + + context.MarkLabel(lbl1); + context.BranchIf(lbl2, rMode, rP, Comparison.NotEqual); + context.Copy(res, EmitUnaryMathCall(context, nameof(Math.Ceiling), op)); + context.Branch(lblEnd); + + context.MarkLabel(lbl2); + context.BranchIf(lbl3, rMode, rM, Comparison.NotEqual); + context.Copy(res, EmitUnaryMathCall(context, nameof(Math.Floor), op)); + context.Branch(lblEnd); + + context.MarkLabel(lbl3); + context.Copy(res, EmitUnaryMathCall(context, nameof(Math.Truncate), op)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + public static Operand EmitSoftFloatCall(ArmEmitterContext context, string name, params Operand[] callArgs) + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + MethodInfo info = (op.Size & 1) == 0 + ? typeof(SoftFloat32).GetMethod(name) + : typeof(SoftFloat64).GetMethod(name); + + context.ExitArmFpMode(); + context.StoreToContext(); + Operand res = context.Call(info, callArgs); + context.LoadFromContext(); + context.EnterArmFpMode(); + + return res; + } + + public static void EmitScalarBinaryOpByElemF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand n = context.VectorExtract(type, GetVec(op.Rn), 0); + Operand m = context.VectorExtract(type, GetVec(op.Rm), op.Index); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), emit(n, m), 0)); + } + + public static void EmitScalarTernaryOpByElemF(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand d = context.VectorExtract(type, GetVec(op.Rd), 0); + Operand n = context.VectorExtract(type, GetVec(op.Rn), 0); + Operand m = context.VectorExtract(type, GetVec(op.Rm), op.Index); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), emit(d, n, m), 0)); + } + + public static void EmitScalarUnaryOpSx(ArmEmitterContext context, Func1I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = EmitVectorExtractSx(context, op.Rn, 0, op.Size); + + Operand d = EmitVectorInsert(context, context.VectorZero(), emit(n), 0, op.Size); + + context.Copy(GetVec(op.Rd), d); + } + + public static void EmitScalarBinaryOpSx(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = EmitVectorExtractSx(context, op.Rn, 0, op.Size); + Operand m = EmitVectorExtractSx(context, op.Rm, 0, op.Size); + + Operand d = EmitVectorInsert(context, context.VectorZero(), emit(n, m), 0, op.Size); + + context.Copy(GetVec(op.Rd), d); + } + + public static void EmitScalarUnaryOpZx(ArmEmitterContext context, Func1I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = EmitVectorExtractZx(context, op.Rn, 0, op.Size); + + Operand d = EmitVectorInsert(context, context.VectorZero(), emit(n), 0, op.Size); + + context.Copy(GetVec(op.Rd), d); + } + + public static void EmitScalarBinaryOpZx(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = EmitVectorExtractZx(context, op.Rn, 0, op.Size); + Operand m = EmitVectorExtractZx(context, op.Rm, 0, op.Size); + + Operand d = EmitVectorInsert(context, context.VectorZero(), emit(n, m), 0, op.Size); + + context.Copy(GetVec(op.Rd), d); + } + + public static void EmitScalarTernaryOpZx(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = EmitVectorExtractZx(context, op.Rd, 0, op.Size); + Operand n = EmitVectorExtractZx(context, op.Rn, 0, op.Size); + Operand m = EmitVectorExtractZx(context, op.Rm, 0, op.Size); + + d = EmitVectorInsert(context, context.VectorZero(), emit(d, n, m), 0, op.Size); + + context.Copy(GetVec(op.Rd), d); + } + + public static void EmitScalarUnaryOpF(ArmEmitterContext context, Func1I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand n = context.VectorExtract(type, GetVec(op.Rn), 0); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), emit(n), 0)); + } + + public static void EmitScalarBinaryOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand n = context.VectorExtract(type, GetVec(op.Rn), 0); + Operand m = context.VectorExtract(type, GetVec(op.Rm), 0); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), emit(n, m), 0)); + } + + public static void EmitScalarTernaryRaOpF(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand a = context.VectorExtract(type, GetVec(op.Ra), 0); + Operand n = context.VectorExtract(type, GetVec(op.Rn), 0); + Operand m = context.VectorExtract(type, GetVec(op.Rm), 0); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), emit(a, n, m), 0)); + } + + public static void EmitVectorUnaryOpF(ArmEmitterContext context, Func1I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, GetVec(op.Rn), index); + + res = context.VectorInsert(res, emit(ne), index); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorBinaryOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, GetVec(op.Rn), index); + Operand me = context.VectorExtract(type, GetVec(op.Rm), index); + + res = context.VectorInsert(res, emit(ne, me), index); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorTernaryOpF(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + for (int index = 0; index < elems; index++) + { + Operand de = context.VectorExtract(type, GetVec(op.Rd), index); + Operand ne = context.VectorExtract(type, GetVec(op.Rn), index); + Operand me = context.VectorExtract(type, GetVec(op.Rm), index); + + res = context.VectorInsert(res, emit(de, ne, me), index); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorBinaryOpByElemF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, GetVec(op.Rn), index); + Operand me = context.VectorExtract(type, GetVec(op.Rm), op.Index); + + res = context.VectorInsert(res, emit(ne, me), index); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorTernaryOpByElemF(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + for (int index = 0; index < elems; index++) + { + Operand de = context.VectorExtract(type, GetVec(op.Rd), index); + Operand ne = context.VectorExtract(type, GetVec(op.Rn), index); + Operand me = context.VectorExtract(type, GetVec(op.Rm), op.Index); + + res = context.VectorInsert(res, emit(de, ne, me), index); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorUnaryOpSx(ArmEmitterContext context, Func1I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractSx(context, op.Rn, index, op.Size); + + res = EmitVectorInsert(context, res, emit(ne), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorBinaryOpSx(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractSx(context, op.Rn, index, op.Size); + Operand me = EmitVectorExtractSx(context, op.Rm, index, op.Size); + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorTernaryOpSx(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtractSx(context, op.Rd, index, op.Size); + Operand ne = EmitVectorExtractSx(context, op.Rn, index, op.Size); + Operand me = EmitVectorExtractSx(context, op.Rm, index, op.Size); + + res = EmitVectorInsert(context, res, emit(de, ne, me), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorUnaryOpZx(ArmEmitterContext context, Func1I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + + res = EmitVectorInsert(context, res, emit(ne), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorBinaryOpZx(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + Operand me = EmitVectorExtractZx(context, op.Rm, index, op.Size); + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorTernaryOpZx(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtractZx(context, op.Rd, index, op.Size); + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + Operand me = EmitVectorExtractZx(context, op.Rm, index, op.Size); + + res = EmitVectorInsert(context, res, emit(de, ne, me), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorBinaryOpByElemSx(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand res = context.VectorZero(); + + Operand me = EmitVectorExtractSx(context, op.Rm, op.Index, op.Size); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractSx(context, op.Rn, index, op.Size); + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorBinaryOpByElemZx(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand res = context.VectorZero(); + + Operand me = EmitVectorExtractZx(context, op.Rm, op.Index, op.Size); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorTernaryOpByElemZx(ArmEmitterContext context, Func3I emit) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand res = context.VectorZero(); + + Operand me = EmitVectorExtractZx(context, op.Rm, op.Index, op.Size); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtractZx(context, op.Rd, index, op.Size); + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + + res = EmitVectorInsert(context, res, emit(de, ne, me), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorImmUnaryOp(ArmEmitterContext context, Func1I emit) + { + OpCodeSimdImm op = (OpCodeSimdImm)context.CurrOp; + + Operand imm = Const(op.Immediate); + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + res = EmitVectorInsert(context, res, emit(imm), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorImmBinaryOp(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdImm op = (OpCodeSimdImm)context.CurrOp; + + Operand imm = Const(op.Immediate); + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtractZx(context, op.Rd, index, op.Size); + + res = EmitVectorInsert(context, res, emit(de, imm), index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorWidenRmBinaryOpSx(ArmEmitterContext context, Func2I emit) + { + EmitVectorWidenRmBinaryOp(context, emit, signed: true); + } + + public static void EmitVectorWidenRmBinaryOpZx(ArmEmitterContext context, Func2I emit) + { + EmitVectorWidenRmBinaryOp(context, emit, signed: false); + } + + private static void EmitVectorWidenRmBinaryOp(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size + 1, signed); + Operand me = EmitVectorExtract(context, op.Rm, part + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size + 1); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorWidenRnRmBinaryOpSx(ArmEmitterContext context, Func2I emit) + { + EmitVectorWidenRnRmBinaryOp(context, emit, signed: true); + } + + public static void EmitVectorWidenRnRmBinaryOpZx(ArmEmitterContext context, Func2I emit) + { + EmitVectorWidenRnRmBinaryOp(context, emit, signed: false); + } + + private static void EmitVectorWidenRnRmBinaryOp(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, part + index, op.Size, signed); + Operand me = EmitVectorExtract(context, op.Rm, part + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size + 1); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorWidenRnRmTernaryOpSx(ArmEmitterContext context, Func3I emit) + { + EmitVectorWidenRnRmTernaryOp(context, emit, signed: true); + } + + public static void EmitVectorWidenRnRmTernaryOpZx(ArmEmitterContext context, Func3I emit) + { + EmitVectorWidenRnRmTernaryOp(context, emit, signed: false); + } + + private static void EmitVectorWidenRnRmTernaryOp(ArmEmitterContext context, Func3I emit, bool signed) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtract(context, op.Rd, index, op.Size + 1, signed); + Operand ne = EmitVectorExtract(context, op.Rn, part + index, op.Size, signed); + Operand me = EmitVectorExtract(context, op.Rm, part + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(de, ne, me), index, op.Size + 1); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorWidenBinaryOpByElemSx(ArmEmitterContext context, Func2I emit) + { + EmitVectorWidenBinaryOpByElem(context, emit, signed: true); + } + + public static void EmitVectorWidenBinaryOpByElemZx(ArmEmitterContext context, Func2I emit) + { + EmitVectorWidenBinaryOpByElem(context, emit, signed: false); + } + + private static void EmitVectorWidenBinaryOpByElem(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand res = context.VectorZero(); + + Operand me = EmitVectorExtract(context, op.Rm, op.Index, op.Size, signed); + + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, part + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size + 1); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorWidenTernaryOpByElemSx(ArmEmitterContext context, Func3I emit) + { + EmitVectorWidenTernaryOpByElem(context, emit, signed: true); + } + + public static void EmitVectorWidenTernaryOpByElemZx(ArmEmitterContext context, Func3I emit) + { + EmitVectorWidenTernaryOpByElem(context, emit, signed: false); + } + + private static void EmitVectorWidenTernaryOpByElem(ArmEmitterContext context, Func3I emit, bool signed) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand res = context.VectorZero(); + + Operand me = EmitVectorExtract(context, op.Rm, op.Index, op.Size, signed); + + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtract(context, op.Rd, index, op.Size + 1, signed); + Operand ne = EmitVectorExtract(context, op.Rn, part + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(de, ne, me), index, op.Size + 1); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitVectorPairwiseOpSx(ArmEmitterContext context, Func2I emit) + { + EmitVectorPairwiseOp(context, emit, signed: true); + } + + public static void EmitVectorPairwiseOpZx(ArmEmitterContext context, Func2I emit) + { + EmitVectorPairwiseOp(context, emit, signed: false); + } + + private static void EmitVectorPairwiseOp(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int pairs = op.GetPairsCount() >> op.Size; + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + + Operand n0 = EmitVectorExtract(context, op.Rn, pairIndex, op.Size, signed); + Operand n1 = EmitVectorExtract(context, op.Rn, pairIndex + 1, op.Size, signed); + + Operand m0 = EmitVectorExtract(context, op.Rm, pairIndex, op.Size, signed); + Operand m1 = EmitVectorExtract(context, op.Rm, pairIndex + 1, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(n0, n1), index, op.Size); + res = EmitVectorInsert(context, res, emit(m0, m1), pairs + index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitSsse3VectorPairwiseOp(ArmEmitterContext context, Intrinsic[] inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd64) + { + Operand zeroEvenMask = X86GetElements(context, ZeroMask, EvenMasks[op.Size]); + Operand zeroOddMask = X86GetElements(context, ZeroMask, OddMasks [op.Size]); + + Operand mN = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, n, m); // m:n + + Operand left = context.AddIntrinsic(Intrinsic.X86Pshufb, mN, zeroEvenMask); // 0:even from m:n + Operand right = context.AddIntrinsic(Intrinsic.X86Pshufb, mN, zeroOddMask); // 0:odd from m:n + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst[op.Size], left, right)); + } + else if (op.Size < 3) + { + Operand oddEvenMask = X86GetElements(context, OddMasks[op.Size], EvenMasks[op.Size]); + + Operand oddEvenN = context.AddIntrinsic(Intrinsic.X86Pshufb, n, oddEvenMask); // odd:even from n + Operand oddEvenM = context.AddIntrinsic(Intrinsic.X86Pshufb, m, oddEvenMask); // odd:even from m + + Operand left = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, oddEvenN, oddEvenM); + Operand right = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, oddEvenN, oddEvenM); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst[op.Size], left, right)); + } + else + { + Operand left = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, n, m); + Operand right = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, n, m); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst[3], left, right)); + } + } + + public static void EmitVectorAcrossVectorOpSx(ArmEmitterContext context, Func2I emit) + { + EmitVectorAcrossVectorOp(context, emit, signed: true, isLong: false); + } + + public static void EmitVectorAcrossVectorOpZx(ArmEmitterContext context, Func2I emit) + { + EmitVectorAcrossVectorOp(context, emit, signed: false, isLong: false); + } + + public static void EmitVectorLongAcrossVectorOpSx(ArmEmitterContext context, Func2I emit) + { + EmitVectorAcrossVectorOp(context, emit, signed: true, isLong: true); + } + + public static void EmitVectorLongAcrossVectorOpZx(ArmEmitterContext context, Func2I emit) + { + EmitVectorAcrossVectorOp(context, emit, signed: false, isLong: true); + } + + private static void EmitVectorAcrossVectorOp( + ArmEmitterContext context, + Func2I emit, + bool signed, + bool isLong) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int elems = op.GetBytesCount() >> op.Size; + + Operand res = EmitVectorExtract(context, op.Rn, 0, op.Size, signed); + + for (int index = 1; index < elems; index++) + { + Operand n = EmitVectorExtract(context, op.Rn, index, op.Size, signed); + + res = emit(res, n); + } + + int size = isLong ? op.Size + 1 : op.Size; + + Operand d = EmitVectorInsert(context, context.VectorZero(), res, 0, size); + + context.Copy(GetVec(op.Rd), d); + } + + public static void EmitVectorAcrossVectorOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Debug.Assert((op.Size & 1) == 0 && op.RegisterSize == RegisterSize.Simd128); + + Operand res = context.VectorExtract(OperandType.FP32, GetVec(op.Rn), 0); + + for (int index = 1; index < 4; index++) + { + Operand n = context.VectorExtract(OperandType.FP32, GetVec(op.Rn), index); + + res = emit(res, n); + } + + Operand d = context.VectorInsert(context.VectorZero(), res, 0); + + context.Copy(GetVec(op.Rd), d); + } + + public static void EmitSse2VectorAcrossVectorOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Debug.Assert((op.Size & 1) == 0 && op.RegisterSize == RegisterSize.Simd128); + + const int sm0 = 0 << 6 | 0 << 4 | 0 << 2 | 0 << 0; + const int sm1 = 1 << 6 | 1 << 4 | 1 << 2 | 1 << 0; + const int sm2 = 2 << 6 | 2 << 4 | 2 << 2 | 2 << 0; + const int sm3 = 3 << 6 | 3 << 4 | 3 << 2 | 3 << 0; + + Operand nCopy = context.Copy(GetVec(op.Rn)); + + Operand part0 = context.AddIntrinsic(Intrinsic.X86Shufps, nCopy, nCopy, Const(sm0)); + Operand part1 = context.AddIntrinsic(Intrinsic.X86Shufps, nCopy, nCopy, Const(sm1)); + Operand part2 = context.AddIntrinsic(Intrinsic.X86Shufps, nCopy, nCopy, Const(sm2)); + Operand part3 = context.AddIntrinsic(Intrinsic.X86Shufps, nCopy, nCopy, Const(sm3)); + + Operand res = emit(emit(part0, part1), emit(part2, part3)); + + context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res)); + } + + public static void EmitScalarPairwiseOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand ne0 = context.VectorExtract(type, GetVec(op.Rn), 0); + Operand ne1 = context.VectorExtract(type, GetVec(op.Rn), 1); + + Operand res = context.VectorInsert(context.VectorZero(), emit(ne0, ne1), 0); + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitSse2ScalarPairwiseOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand op0, op1; + + if ((op.Size & 1) == 0) + { + const int sm0 = 2 << 6 | 2 << 4 | 2 << 2 | 0 << 0; + const int sm1 = 2 << 6 | 2 << 4 | 2 << 2 | 1 << 0; + + Operand zeroN = context.VectorZeroUpper64(n); + + op0 = context.AddIntrinsic(Intrinsic.X86Pshufd, zeroN, Const(sm0)); + op1 = context.AddIntrinsic(Intrinsic.X86Pshufd, zeroN, Const(sm1)); + } + else /* if ((op.Size & 1) == 1) */ + { + Operand zero = context.VectorZero(); + + op0 = context.AddIntrinsic(Intrinsic.X86Movlhps, n, zero); + op1 = context.AddIntrinsic(Intrinsic.X86Movhlps, zero, n); + } + + context.Copy(GetVec(op.Rd), emit(op0, op1)); + } + + public static void EmitVectorPairwiseOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int pairs = op.GetPairsCount() >> sizeF + 2; + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + + Operand n0 = context.VectorExtract(type, GetVec(op.Rn), pairIndex); + Operand n1 = context.VectorExtract(type, GetVec(op.Rn), pairIndex + 1); + + Operand m0 = context.VectorExtract(type, GetVec(op.Rm), pairIndex); + Operand m1 = context.VectorExtract(type, GetVec(op.Rm), pairIndex + 1); + + res = context.VectorInsert(res, emit(n0, n1), index); + res = context.VectorInsert(res, emit(m0, m1), pairs + index); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitSse2VectorPairwiseOpF(ArmEmitterContext context, Func2I emit) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand nCopy = context.Copy(GetVec(op.Rn)); + Operand mCopy = context.Copy(GetVec(op.Rm)); + + int sizeF = op.Size & 1; + + if (sizeF == 0) + { + if (op.RegisterSize == RegisterSize.Simd64) + { + Operand unpck = context.AddIntrinsic(Intrinsic.X86Unpcklps, nCopy, mCopy); + + Operand zero = context.VectorZero(); + + Operand part0 = context.AddIntrinsic(Intrinsic.X86Movlhps, unpck, zero); + Operand part1 = context.AddIntrinsic(Intrinsic.X86Movhlps, zero, unpck); + + context.Copy(GetVec(op.Rd), emit(part0, part1)); + } + else /* if (op.RegisterSize == RegisterSize.Simd128) */ + { + const int sm0 = 2 << 6 | 0 << 4 | 2 << 2 | 0 << 0; + const int sm1 = 3 << 6 | 1 << 4 | 3 << 2 | 1 << 0; + + Operand part0 = context.AddIntrinsic(Intrinsic.X86Shufps, nCopy, mCopy, Const(sm0)); + Operand part1 = context.AddIntrinsic(Intrinsic.X86Shufps, nCopy, mCopy, Const(sm1)); + + context.Copy(GetVec(op.Rd), emit(part0, part1)); + } + } + else /* if (sizeF == 1) */ + { + Operand part0 = context.AddIntrinsic(Intrinsic.X86Unpcklpd, nCopy, mCopy); + Operand part1 = context.AddIntrinsic(Intrinsic.X86Unpckhpd, nCopy, mCopy); + + context.Copy(GetVec(op.Rd), emit(part0, part1)); + } + } + + public enum CmpCondition + { + // Legacy Sse. + Equal = 0, // Ordered, non-signaling. + LessThan = 1, // Ordered, signaling. + LessThanOrEqual = 2, // Ordered, signaling. + UnorderedQ = 3, // Non-signaling. + NotLessThan = 5, // Unordered, signaling. + NotLessThanOrEqual = 6, // Unordered, signaling. + OrderedQ = 7, // Non-signaling. + + // Vex. + GreaterThanOrEqual = 13, // Ordered, signaling. + GreaterThan = 14, // Ordered, signaling. + OrderedS = 23 // Signaling. + } + + [Flags] + public enum SaturatingFlags + { + None = 0, + + ByElem = 1 << 0, + Scalar = 1 << 1, + Signed = 1 << 2, + + Add = 1 << 3, + Sub = 1 << 4, + + Accumulate = 1 << 5 + } + + public static void EmitScalarSaturatingUnaryOpSx(ArmEmitterContext context, Func1I emit) + { + EmitSaturatingUnaryOpSx(context, emit, SaturatingFlags.Scalar | SaturatingFlags.Signed); + } + + public static void EmitVectorSaturatingUnaryOpSx(ArmEmitterContext context, Func1I emit) + { + EmitSaturatingUnaryOpSx(context, emit, SaturatingFlags.Signed); + } + + public static void EmitSaturatingUnaryOpSx(ArmEmitterContext context, Func1I emit, SaturatingFlags flags) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + bool scalar = (flags & SaturatingFlags.Scalar) != 0; + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractSx(context, op.Rn, index, op.Size); + Operand de; + + if (op.Size <= 2) + { + de = EmitSignedSrcSatQ(context, emit(ne), op.Size, signedDst: true); + } + else /* if (op.Size == 3) */ + { + de = EmitUnarySignedSatQAbsOrNeg(context, emit(ne)); + } + + res = EmitVectorInsert(context, res, de, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + public static void EmitScalarSaturatingBinaryOpSx(ArmEmitterContext context, Func2I emit = null, SaturatingFlags flags = SaturatingFlags.None) + { + EmitSaturatingBinaryOp(context, emit, SaturatingFlags.Scalar | SaturatingFlags.Signed | flags); + } + + public static void EmitScalarSaturatingBinaryOpZx(ArmEmitterContext context, SaturatingFlags flags) + { + EmitSaturatingBinaryOp(context, null, SaturatingFlags.Scalar | flags); + } + + public static void EmitVectorSaturatingBinaryOpSx(ArmEmitterContext context, Func2I emit = null, SaturatingFlags flags = SaturatingFlags.None) + { + EmitSaturatingBinaryOp(context, emit, SaturatingFlags.Signed | flags); + } + + public static void EmitVectorSaturatingBinaryOpZx(ArmEmitterContext context, SaturatingFlags flags) + { + EmitSaturatingBinaryOp(context, null, flags); + } + + public static void EmitVectorSaturatingBinaryOpByElemSx(ArmEmitterContext context, Func2I emit) + { + EmitSaturatingBinaryOp(context, emit, SaturatingFlags.ByElem | SaturatingFlags.Signed); + } + + public static void EmitSaturatingBinaryOp(ArmEmitterContext context, Func2I emit, SaturatingFlags flags) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + bool byElem = (flags & SaturatingFlags.ByElem) != 0; + bool scalar = (flags & SaturatingFlags.Scalar) != 0; + bool signed = (flags & SaturatingFlags.Signed) != 0; + + bool add = (flags & SaturatingFlags.Add) != 0; + bool sub = (flags & SaturatingFlags.Sub) != 0; + + bool accumulate = (flags & SaturatingFlags.Accumulate) != 0; + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + if (add || sub) + { + for (int index = 0; index < elems; index++) + { + Operand de; + Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size, signed); + Operand me = EmitVectorExtract(context, ((OpCodeSimdReg)op).Rm, index, op.Size, signed); + + if (op.Size <= 2) + { + Operand temp = add ? context.Add(ne, me) : context.Subtract(ne, me); + + de = EmitSignedSrcSatQ(context, temp, op.Size, signedDst: signed); + } + else /* if (op.Size == 3) */ + { + if (add) + { + de = signed ? EmitBinarySignedSatQAdd(context, ne, me) : EmitBinaryUnsignedSatQAdd(context, ne, me); + } + else /* if (sub) */ + { + de = signed ? EmitBinarySignedSatQSub(context, ne, me) : EmitBinaryUnsignedSatQSub(context, ne, me); + } + } + + res = EmitVectorInsert(context, res, de, index, op.Size); + } + } + else if (accumulate) + { + for (int index = 0; index < elems; index++) + { + Operand de; + Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size, !signed); + Operand me = EmitVectorExtract(context, op.Rd, index, op.Size, signed); + + if (op.Size <= 2) + { + Operand temp = context.Add(ne, me); + + de = EmitSignedSrcSatQ(context, temp, op.Size, signedDst: signed); + } + else /* if (op.Size == 3) */ + { + de = signed ? EmitBinarySignedSatQAcc(context, ne, me) : EmitBinaryUnsignedSatQAcc(context, ne, me); + } + + res = EmitVectorInsert(context, res, de, index, op.Size); + } + } + else + { + Operand me = default; + + if (byElem) + { + OpCodeSimdRegElem opRegElem = (OpCodeSimdRegElem)op; + + me = EmitVectorExtract(context, opRegElem.Rm, opRegElem.Index, op.Size, signed); + } + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size, signed); + + if (!byElem) + { + me = EmitVectorExtract(context, ((OpCodeSimdReg)op).Rm, index, op.Size, signed); + } + + Operand de = EmitSignedSrcSatQ(context, emit(ne, me), op.Size, signedDst: signed); + + res = EmitVectorInsert(context, res, de, index, op.Size); + } + } + + context.Copy(GetVec(op.Rd), res); + } + + [Flags] + public enum SaturatingNarrowFlags + { + Scalar = 1 << 0, + SignedSrc = 1 << 1, + SignedDst = 1 << 2, + + ScalarSxSx = Scalar | SignedSrc | SignedDst, + ScalarSxZx = Scalar | SignedSrc, + ScalarZxZx = Scalar, + + VectorSxSx = SignedSrc | SignedDst, + VectorSxZx = SignedSrc, + VectorZxZx = 0 + } + + public static void EmitSaturatingNarrowOp(ArmEmitterContext context, SaturatingNarrowFlags flags) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + bool scalar = (flags & SaturatingNarrowFlags.Scalar) != 0; + bool signedSrc = (flags & SaturatingNarrowFlags.SignedSrc) != 0; + bool signedDst = (flags & SaturatingNarrowFlags.SignedDst) != 0; + + int elems = !scalar ? 8 >> op.Size : 1; + + int part = !scalar && (op.RegisterSize == RegisterSize.Simd128) ? elems : 0; + + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size + 1, signedSrc); + + Operand temp = signedSrc + ? EmitSignedSrcSatQ(context, ne, op.Size, signedDst) + : EmitUnsignedSrcSatQ(context, ne, op.Size, signedDst); + + res = EmitVectorInsert(context, res, temp, part + index, op.Size); + } + + context.Copy(d, res); + } + + // long SignedSignSatQ(long op, int size); + public static Operand EmitSignedSignSatQ(ArmEmitterContext context, Operand op, int size) + { + int eSize = 8 << size; + + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64); + + Operand lbl1 = Label(); + Operand lblEnd = Label(); + + Operand zeroL = Const(0L); + Operand maxT = Const((1L << (eSize - 1)) - 1L); + Operand minT = Const(-(1L << (eSize - 1))); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), zeroL); + + context.BranchIf(lbl1, op, zeroL, Comparison.LessOrEqual); + context.Copy(res, maxT); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lbl1); + context.BranchIf(lblEnd, op, zeroL, Comparison.GreaterOrEqual); + context.Copy(res, minT); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // private static ulong UnsignedSignSatQ(ulong op, int size); + public static Operand EmitUnsignedSignSatQ(ArmEmitterContext context, Operand op, int size) + { + int eSize = 8 << size; + + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64); + + Operand lblEnd = Label(); + + Operand zeroUL = Const(0UL); + Operand maxT = Const(ulong.MaxValue >> (64 - eSize)); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), zeroUL); + + context.BranchIf(lblEnd, op, zeroUL, Comparison.LessOrEqualUI); + context.Copy(res, maxT); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // TSrc (16bit, 32bit, 64bit; signed) > TDst (8bit, 16bit, 32bit; signed, unsigned). + // long SignedSrcSignedDstSatQ(long op, int size); ulong SignedSrcUnsignedDstSatQ(long op, int size); + public static Operand EmitSignedSrcSatQ(ArmEmitterContext context, Operand op, int sizeDst, bool signedDst) + { + int eSizeDst = 8 << sizeDst; + + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(eSizeDst == 8 || eSizeDst == 16 || eSizeDst == 32); + + Operand lbl1 = Label(); + Operand lblEnd = Label(); + + Operand maxT = signedDst ? Const((1L << (eSizeDst - 1)) - 1L) : Const((1UL << eSizeDst) - 1UL); + Operand minT = signedDst ? Const(-(1L << (eSizeDst - 1))) : Const(0UL); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), op); + + context.BranchIf(lbl1, op, maxT, Comparison.LessOrEqual); + context.Copy(res, maxT); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lbl1); + context.BranchIf(lblEnd, op, minT, Comparison.GreaterOrEqual); + context.Copy(res, minT); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // TSrc (16bit, 32bit, 64bit; unsigned) > TDst (8bit, 16bit, 32bit; signed, unsigned). + // long UnsignedSrcSignedDstSatQ(ulong op, int size); ulong UnsignedSrcUnsignedDstSatQ(ulong op, int size); + public static Operand EmitUnsignedSrcSatQ(ArmEmitterContext context, Operand op, int sizeDst, bool signedDst) + { + int eSizeDst = 8 << sizeDst; + + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(eSizeDst == 8 || eSizeDst == 16 || eSizeDst == 32); + + Operand lblEnd = Label(); + + Operand maxT = signedDst ? Const((1L << (eSizeDst - 1)) - 1L) : Const((1UL << eSizeDst) - 1UL); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), op); + + context.BranchIf(lblEnd, op, maxT, Comparison.LessOrEqualUI); + context.Copy(res, maxT); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // long UnarySignedSatQAbsOrNeg(long op); + private static Operand EmitUnarySignedSatQAbsOrNeg(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I64); + + Operand lblEnd = Label(); + + Operand minL = Const(long.MinValue); + Operand maxL = Const(long.MaxValue); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), op); + + context.BranchIf(lblEnd, op, minL, Comparison.NotEqual); + context.Copy(res, maxL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // long BinarySignedSatQAdd(long op1, long op2); + public static Operand EmitBinarySignedSatQAdd(ArmEmitterContext context, Operand op1, Operand op2) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand lblEnd = Label(); + + Operand minL = Const(long.MinValue); + Operand maxL = Const(long.MaxValue); + Operand zeroL = Const(0L); + + Operand add = context.Add(op1, op2); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), add); + + Operand left = context.BitwiseNot(context.BitwiseExclusiveOr(op1, op2)); + Operand right = context.BitwiseExclusiveOr(op1, add); + context.BranchIf(lblEnd, context.BitwiseAnd(left, right), zeroL, Comparison.GreaterOrEqual); + + Operand isPositive = context.ICompareGreaterOrEqual(op1, zeroL); + context.Copy(res, context.ConditionalSelect(isPositive, maxL, minL)); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // ulong BinaryUnsignedSatQAdd(ulong op1, ulong op2); + public static Operand EmitBinaryUnsignedSatQAdd(ArmEmitterContext context, Operand op1, Operand op2) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand lblEnd = Label(); + + Operand maxUL = Const(ulong.MaxValue); + + Operand add = context.Add(op1, op2); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), add); + + context.BranchIf(lblEnd, add, op1, Comparison.GreaterOrEqualUI); + context.Copy(res, maxUL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // long BinarySignedSatQSub(long op1, long op2); + public static Operand EmitBinarySignedSatQSub(ArmEmitterContext context, Operand op1, Operand op2) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand lblEnd = Label(); + + Operand minL = Const(long.MinValue); + Operand maxL = Const(long.MaxValue); + Operand zeroL = Const(0L); + + Operand sub = context.Subtract(op1, op2); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), sub); + + Operand left = context.BitwiseExclusiveOr(op1, op2); + Operand right = context.BitwiseExclusiveOr(op1, sub); + context.BranchIf(lblEnd, context.BitwiseAnd(left, right), zeroL, Comparison.GreaterOrEqual); + + Operand isPositive = context.ICompareGreaterOrEqual(op1, zeroL); + context.Copy(res, context.ConditionalSelect(isPositive, maxL, minL)); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // ulong BinaryUnsignedSatQSub(ulong op1, ulong op2); + public static Operand EmitBinaryUnsignedSatQSub(ArmEmitterContext context, Operand op1, Operand op2) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand lblEnd = Label(); + + Operand zeroL = Const(0L); + + Operand sub = context.Subtract(op1, op2); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), sub); + + context.BranchIf(lblEnd, op1, op2, Comparison.GreaterOrEqualUI); + context.Copy(res, zeroL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // long BinarySignedSatQAcc(ulong op1, long op2); + private static Operand EmitBinarySignedSatQAcc(ArmEmitterContext context, Operand op1, Operand op2) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand lbl1 = Label(); + Operand lbl2 = Label(); + Operand lblEnd = Label(); + + Operand maxL = Const(long.MaxValue); + Operand zeroL = Const(0L); + + Operand add = context.Add(op1, op2); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), add); + + context.BranchIf(lbl1, op1, maxL, Comparison.GreaterUI); + Operand notOp2AndRes = context.BitwiseAnd(context.BitwiseNot(op2), add); + context.BranchIf(lblEnd, notOp2AndRes, zeroL, Comparison.GreaterOrEqual); + context.Copy(res, maxL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lbl1); + context.BranchIf(lbl2, op2, zeroL, Comparison.Less); + context.Copy(res, maxL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lbl2); + context.BranchIf(lblEnd, add, maxL, Comparison.LessOrEqualUI); + context.Copy(res, maxL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // ulong BinaryUnsignedSatQAcc(long op1, ulong op2); + private static Operand EmitBinaryUnsignedSatQAcc(ArmEmitterContext context, Operand op1, Operand op2) + { + Debug.Assert(op1.Type == OperandType.I64 && op2.Type == OperandType.I64); + + Operand lbl1 = Label(); + Operand lblEnd = Label(); + + Operand maxUL = Const(ulong.MaxValue); + Operand maxL = Const(long.MaxValue); + Operand zeroL = Const(0L); + + Operand add = context.Add(op1, op2); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), add); + + context.BranchIf(lbl1, op1, zeroL, Comparison.Less); + context.BranchIf(lblEnd, add, op1, Comparison.GreaterOrEqualUI); + context.Copy(res, maxUL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lbl1); + context.BranchIf(lblEnd, op2, maxL, Comparison.GreaterUI); + context.BranchIf(lblEnd, add, zeroL, Comparison.GreaterOrEqual); + context.Copy(res, zeroL); + SetFpFlag(context, FPState.QcFlag, Const(1)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + public static Operand EmitFloatAbs(ArmEmitterContext context, Operand value, bool single, bool vector) + { + Operand mask; + if (single) + { + mask = vector ? X86GetAllElements(context, -0f) : X86GetScalar(context, -0f); + } + else + { + mask = vector ? X86GetAllElements(context, -0d) : X86GetScalar(context, -0d); + } + + return context.AddIntrinsic(single ? Intrinsic.X86Andnps : Intrinsic.X86Andnpd, mask, value); + } + + public static Operand EmitVectorExtractSx(ArmEmitterContext context, int reg, int index, int size) + { + return EmitVectorExtract(context, reg, index, size, true); + } + + public static Operand EmitVectorExtractZx(ArmEmitterContext context, int reg, int index, int size) + { + return EmitVectorExtract(context, reg, index, size, false); + } + + public static Operand EmitVectorExtract(ArmEmitterContext context, int reg, int index, int size, bool signed) + { + ThrowIfInvalid(index, size); + + Operand res = default; + + switch (size) + { + case 0: + res = context.VectorExtract8(GetVec(reg), index); + break; + + case 1: + res = context.VectorExtract16(GetVec(reg), index); + break; + + case 2: + res = context.VectorExtract(OperandType.I32, GetVec(reg), index); + break; + + case 3: + res = context.VectorExtract(OperandType.I64, GetVec(reg), index); + break; + } + + if (signed) + { + switch (size) + { + case 0: res = context.SignExtend8 (OperandType.I64, res); break; + case 1: res = context.SignExtend16(OperandType.I64, res); break; + case 2: res = context.SignExtend32(OperandType.I64, res); break; + } + } + else + { + switch (size) + { + case 0: res = context.ZeroExtend8 (OperandType.I64, res); break; + case 1: res = context.ZeroExtend16(OperandType.I64, res); break; + case 2: res = context.ZeroExtend32(OperandType.I64, res); break; + } + } + + return res; + } + + public static Operand EmitVectorInsert(ArmEmitterContext context, Operand vector, Operand value, int index, int size) + { + ThrowIfInvalid(index, size); + + if (size < 3 && value.Type == OperandType.I64) + { + value = context.ConvertI64ToI32(value); + } + + switch (size) + { + case 0: vector = context.VectorInsert8 (vector, value, index); break; + case 1: vector = context.VectorInsert16(vector, value, index); break; + case 2: vector = context.VectorInsert (vector, value, index); break; + case 3: vector = context.VectorInsert (vector, value, index); break; + } + + return vector; + } + + public static void ThrowIfInvalid(int index, int size) + { + if ((uint)size > 3u) + { + throw new ArgumentOutOfRangeException(nameof(size)); + } + + if ((uint)index >= 16u >> size) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdHelper32.cs b/src/ARMeilleure/Instructions/InstEmitSimdHelper32.cs new file mode 100644 index 00000000..36d27d42 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdHelper32.cs @@ -0,0 +1,1286 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using System.Reflection; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + using Func1I = Func; + using Func2I = Func; + using Func3I = Func; + + static class InstEmitSimdHelper32 + { + public static (int, int) GetQuadwordAndSubindex(int index, RegisterSize size) + { + switch (size) + { + case RegisterSize.Simd128: + return (index >> 1, 0); + case RegisterSize.Simd64: + case RegisterSize.Int64: + return (index >> 1, index & 1); + case RegisterSize.Int32: + return (index >> 2, index & 3); + } + + throw new ArgumentException("Unrecognized Vector Register Size."); + } + + public static Operand ExtractScalar(ArmEmitterContext context, OperandType type, int reg) + { + Debug.Assert(type != OperandType.V128); + + if (type == OperandType.FP64 || type == OperandType.I64) + { + // From dreg. + return context.VectorExtract(type, GetVecA32(reg >> 1), reg & 1); + } + else + { + // From sreg. + return context.VectorExtract(type, GetVecA32(reg >> 2), reg & 3); + } + } + + public static void InsertScalar(ArmEmitterContext context, int reg, Operand value) + { + Debug.Assert(value.Type != OperandType.V128); + + Operand vec, insert; + if (value.Type == OperandType.FP64 || value.Type == OperandType.I64) + { + // From dreg. + vec = GetVecA32(reg >> 1); + insert = context.VectorInsert(vec, value, reg & 1); + } + else + { + // From sreg. + vec = GetVecA32(reg >> 2); + insert = context.VectorInsert(vec, value, reg & 3); + } + + context.Copy(vec, insert); + } + + public static Operand ExtractScalar16(ArmEmitterContext context, int reg, bool top) + { + return context.VectorExtract16(GetVecA32(reg >> 2), ((reg & 3) << 1) | (top ? 1 : 0)); + } + + public static void InsertScalar16(ArmEmitterContext context, int reg, bool top, Operand value) + { + Debug.Assert(value.Type == OperandType.FP32 || value.Type == OperandType.I32); + + Operand vec, insert; + vec = GetVecA32(reg >> 2); + insert = context.VectorInsert16(vec, value, ((reg & 3) << 1) | (top ? 1 : 0)); + + context.Copy(vec, insert); + } + + public static Operand ExtractElement(ArmEmitterContext context, int reg, int size, bool signed) + { + return EmitVectorExtract32(context, reg >> (4 - size), reg & ((16 >> size) - 1), size, signed); + } + + public static void EmitVectorImmUnaryOp32(ArmEmitterContext context, Func1I emit) + { + IOpCode32SimdImm op = (IOpCode32SimdImm)context.CurrOp; + + Operand imm = Const(op.Immediate); + + int elems = op.Elems; + (int index, int subIndex) = GetQuadwordAndSubindex(op.Vd, op.RegisterSize); + + Operand vec = GetVecA32(index); + Operand res = vec; + + for (int item = 0; item < elems; item++) + { + res = EmitVectorInsert(context, res, emit(imm), item + subIndex * elems, op.Size); + } + + context.Copy(vec, res); + } + + public static void EmitScalarUnaryOpF32(ArmEmitterContext context, Func1I emit) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand m = ExtractScalar(context, type, op.Vm); + + InsertScalar(context, op.Vd, emit(m)); + } + + public static void EmitScalarBinaryOpF32(ArmEmitterContext context, Func2I emit) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand n = ExtractScalar(context, type, op.Vn); + Operand m = ExtractScalar(context, type, op.Vm); + + InsertScalar(context, op.Vd, emit(n, m)); + } + + public static void EmitScalarBinaryOpI32(ArmEmitterContext context, Func2I emit) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.I64 : OperandType.I32; + + if (op.Size < 2) + { + throw new NotSupportedException("Cannot perform a scalar SIMD operation on integers smaller than 32 bits."); + } + + Operand n = ExtractScalar(context, type, op.Vn); + Operand m = ExtractScalar(context, type, op.Vm); + + InsertScalar(context, op.Vd, emit(n, m)); + } + + public static void EmitScalarTernaryOpF32(ArmEmitterContext context, Func3I emit) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + OperandType type = (op.Size & 1) != 0 ? OperandType.FP64 : OperandType.FP32; + + Operand a = ExtractScalar(context, type, op.Vd); + Operand n = ExtractScalar(context, type, op.Vn); + Operand m = ExtractScalar(context, type, op.Vm); + + InsertScalar(context, op.Vd, emit(a, n, m)); + } + + public static void EmitVectorUnaryOpF32(ArmEmitterContext context, Func1I emit) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < elems; index++) + { + Operand me = context.VectorExtract(type, GetVecA32(op.Qm), op.Fm + index); + + res = context.VectorInsert(res, emit(me), op.Fd + index); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorBinaryOpF32(ArmEmitterContext context, Func2I emit) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> (sizeF + 2); + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, GetVecA32(op.Qn), op.Fn + index); + Operand me = context.VectorExtract(type, GetVecA32(op.Qm), op.Fm + index); + + res = context.VectorInsert(res, emit(ne, me), op.Fd + index); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorTernaryOpF32(ArmEmitterContext context, Func3I emit) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < elems; index++) + { + Operand de = context.VectorExtract(type, GetVecA32(op.Qd), op.Fd + index); + Operand ne = context.VectorExtract(type, GetVecA32(op.Qn), op.Fn + index); + Operand me = context.VectorExtract(type, GetVecA32(op.Qm), op.Fm + index); + + res = context.VectorInsert(res, emit(de, ne, me), op.Fd + index); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + // Integer + + public static void EmitVectorUnaryAccumulateOpI32(ArmEmitterContext context, Func1I emit, bool signed) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size, signed); + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + res = EmitVectorInsert(context, res, context.Add(de, emit(me)), op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorUnaryOpI32(ArmEmitterContext context, Func1I emit, bool signed) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(me), op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorBinaryOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size, signed); + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(ne, me), op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorBinaryLongOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size, signed); + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + if (op.Size == 2) + { + ne = signed ? context.SignExtend32(OperandType.I64, ne) : context.ZeroExtend32(OperandType.I64, ne); + me = signed ? context.SignExtend32(OperandType.I64, me) : context.ZeroExtend32(OperandType.I64, me); + } + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorBinaryWideOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size + 1, signed); + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + if (op.Size == 2) + { + me = signed ? context.SignExtend32(OperandType.I64, me) : context.ZeroExtend32(OperandType.I64, me); + } + + res = EmitVectorInsert(context, res, emit(ne, me), index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorImmBinaryQdQmOpZx32(ArmEmitterContext context, Func2I emit) + { + EmitVectorImmBinaryQdQmOpI32(context, emit, false); + } + + public static void EmitVectorImmBinaryQdQmOpSx32(ArmEmitterContext context, Func2I emit) + { + EmitVectorImmBinaryQdQmOpI32(context, emit, true); + } + + public static void EmitVectorImmBinaryQdQmOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size, signed); + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(de, me), op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorTernaryLongOpI32(ArmEmitterContext context, Func3I emit, bool signed) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size + 1, signed); + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size, signed); + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + if (op.Size == 2) + { + ne = signed ? context.SignExtend32(OperandType.I64, ne) : context.ZeroExtend32(OperandType.I64, ne); + me = signed ? context.SignExtend32(OperandType.I64, me) : context.ZeroExtend32(OperandType.I64, me); + } + + res = EmitVectorInsert(context, res, emit(de, ne, me), index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorTernaryOpI32(ArmEmitterContext context, Func3I emit, bool signed) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size, signed); + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size, signed); + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(de, ne, me), op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorUnaryOpSx32(ArmEmitterContext context, Func1I emit) + { + EmitVectorUnaryOpI32(context, emit, true); + } + + public static void EmitVectorUnaryOpSx32(ArmEmitterContext context, Func1I emit, bool accumulate) + { + if (accumulate) + { + EmitVectorUnaryAccumulateOpI32(context, emit, true); + } + else + { + EmitVectorUnaryOpI32(context, emit, true); + } + } + + public static void EmitVectorBinaryOpSx32(ArmEmitterContext context, Func2I emit) + { + EmitVectorBinaryOpI32(context, emit, true); + } + + public static void EmitVectorTernaryOpSx32(ArmEmitterContext context, Func3I emit) + { + EmitVectorTernaryOpI32(context, emit, true); + } + + public static void EmitVectorUnaryOpZx32(ArmEmitterContext context, Func1I emit) + { + EmitVectorUnaryOpI32(context, emit, false); + } + + public static void EmitVectorUnaryOpZx32(ArmEmitterContext context, Func1I emit, bool accumulate) + { + if (accumulate) + { + EmitVectorUnaryAccumulateOpI32(context, emit, false); + } + else + { + EmitVectorUnaryOpI32(context, emit, false); + } + } + + public static void EmitVectorBinaryOpZx32(ArmEmitterContext context, Func2I emit) + { + EmitVectorBinaryOpI32(context, emit, false); + } + + public static void EmitVectorTernaryOpZx32(ArmEmitterContext context, Func3I emit) + { + EmitVectorTernaryOpI32(context, emit, false); + } + + // Vector by scalar + + public static void EmitVectorByScalarOpF32(ArmEmitterContext context, Func2I emit) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + Operand m = ExtractScalar(context, type, op.Vm); + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < elems; index++) + { + Operand ne = context.VectorExtract(type, GetVecA32(op.Qn), op.Fn + index); + + res = context.VectorInsert(res, emit(ne, m), op.Fd + index); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorByScalarOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + Operand m = ExtractElement(context, op.Vm, op.Size, signed); + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(ne, m), op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorByScalarLongOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + Operand m = ExtractElement(context, op.Vm, op.Size, signed); + + if (op.Size == 2) + { + m = signed ? context.SignExtend32(OperandType.I64, m) : context.ZeroExtend32(OperandType.I64, m); + } + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size, signed); + + if (op.Size == 2) + { + ne = signed ? context.SignExtend32(OperandType.I64, ne) : context.ZeroExtend32(OperandType.I64, ne); + } + + res = EmitVectorInsert(context, res, emit(ne, m), index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorsByScalarOpF32(ArmEmitterContext context, Func3I emit) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> sizeF + 2; + + Operand m = ExtractScalar(context, type, op.Vm); + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < elems; index++) + { + Operand de = context.VectorExtract(type, GetVecA32(op.Qd), op.Fd + index); + Operand ne = context.VectorExtract(type, GetVecA32(op.Qn), op.Fn + index); + + res = context.VectorInsert(res, emit(de, ne, m), op.Fd + index); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorsByScalarOpI32(ArmEmitterContext context, Func3I emit, bool signed) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + Operand m = EmitVectorExtract32(context, op.Vm >> (4 - op.Size), op.Vm & ((1 << (4 - op.Size)) - 1), op.Size, signed); + + Operand res = GetVecA32(op.Qd); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand de = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size, signed); + Operand ne = EmitVectorExtract32(context, op.Qn, op.In + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(de, ne, m), op.Id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + // Pairwise + + public static void EmitVectorPairwiseOpF32(ArmEmitterContext context, Func2I emit) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + int sizeF = op.Size & 1; + + OperandType type = sizeF != 0 ? OperandType.FP64 : OperandType.FP32; + + int elems = op.GetBytesCount() >> (sizeF + 2); + int pairs = elems >> 1; + + Operand res = GetVecA32(op.Qd); + Operand mvec = GetVecA32(op.Qm); + Operand nvec = GetVecA32(op.Qn); + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + + Operand n1 = context.VectorExtract(type, nvec, op.Fn + pairIndex); + Operand n2 = context.VectorExtract(type, nvec, op.Fn + pairIndex + 1); + + res = context.VectorInsert(res, emit(n1, n2), op.Fd + index); + + Operand m1 = context.VectorExtract(type, mvec, op.Fm + pairIndex); + Operand m2 = context.VectorExtract(type, mvec, op.Fm + pairIndex + 1); + + res = context.VectorInsert(res, emit(m1, m2), op.Fd + index + pairs); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorPairwiseOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + int elems = op.GetBytesCount() >> op.Size; + int pairs = elems >> 1; + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + Operand n1 = EmitVectorExtract32(context, op.Qn, op.In + pairIndex, op.Size, signed); + Operand n2 = EmitVectorExtract32(context, op.Qn, op.In + pairIndex + 1, op.Size, signed); + + Operand m1 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex, op.Size, signed); + Operand m2 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex + 1, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(n1, n2), op.Id + index, op.Size); + res = EmitVectorInsert(context, res, emit(m1, m2), op.Id + index + pairs, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void EmitVectorPairwiseLongOpI32(ArmEmitterContext context, Func2I emit, bool signed) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + int elems = (op.Q ? 16 : 8) >> op.Size; + int pairs = elems >> 1; + int id = (op.Vd & 1) * pairs; + + Operand res = GetVecA32(op.Qd); + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + Operand m1 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex, op.Size, signed); + Operand m2 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex + 1, op.Size, signed); + + if (op.Size == 2) + { + m1 = signed ? context.SignExtend32(OperandType.I64, m1) : context.ZeroExtend32(OperandType.I64, m1); + m2 = signed ? context.SignExtend32(OperandType.I64, m2) : context.ZeroExtend32(OperandType.I64, m2); + } + + res = EmitVectorInsert(context, res, emit(m1, m2), id + index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + // Narrow + + public static void EmitVectorUnaryNarrowOp32(ArmEmitterContext context, Func1I emit, bool signed = false) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + int elems = 8 >> op.Size; // Size contains the target element size. (for when it becomes a doubleword) + + Operand res = GetVecA32(op.Qd); + int id = (op.Vd & 1) << (3 - op.Size); // Target doubleword base. + + for (int index = 0; index < elems; index++) + { + Operand m = EmitVectorExtract32(context, op.Qm, index, op.Size + 1, signed); + + res = EmitVectorInsert(context, res, emit(m), id + index, op.Size); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + // Intrinsic Helpers + + public static Operand EmitMoveDoubleWordToSide(ArmEmitterContext context, Operand input, int originalV, int targetV) + { + Debug.Assert(input.Type == OperandType.V128); + + int originalSide = originalV & 1; + int targetSide = targetV & 1; + + if (originalSide == targetSide) + { + return input; + } + + if (targetSide == 1) + { + return context.AddIntrinsic(Intrinsic.X86Movlhps, input, input); // Low to high. + } + else + { + return context.AddIntrinsic(Intrinsic.X86Movhlps, input, input); // High to low. + } + } + + public static Operand EmitDoubleWordInsert(ArmEmitterContext context, Operand target, Operand value, int targetV) + { + Debug.Assert(target.Type == OperandType.V128 && value.Type == OperandType.V128); + + int targetSide = targetV & 1; + int shuffleMask = 2; + + if (targetSide == 1) + { + return context.AddIntrinsic(Intrinsic.X86Shufpd, target, value, Const(shuffleMask)); + } + else + { + return context.AddIntrinsic(Intrinsic.X86Shufpd, value, target, Const(shuffleMask)); + } + } + + public static Operand EmitScalarInsert(ArmEmitterContext context, Operand target, Operand value, int reg, bool doubleWidth) + { + Debug.Assert(target.Type == OperandType.V128 && value.Type == OperandType.V128); + + // Insert from index 0 in value to index in target. + int index = reg & (doubleWidth ? 1 : 3); + + if (doubleWidth) + { + if (index == 1) + { + return context.AddIntrinsic(Intrinsic.X86Movlhps, target, value); // Low to high. + } + else + { + return context.AddIntrinsic(Intrinsic.X86Shufpd, value, target, Const(2)); // Low to low, keep high from original. + } + } + else + { + if (Optimizations.UseSse41) + { + return context.AddIntrinsic(Intrinsic.X86Insertps, target, value, Const(index << 4)); + } + else + { + target = EmitSwapScalar(context, target, index, doubleWidth); // Swap value to replace into element 0. + target = context.AddIntrinsic(Intrinsic.X86Movss, target, value); // Move the value into element 0 of the vector. + return EmitSwapScalar(context, target, index, doubleWidth); // Swap new value back to the correct index. + } + } + } + + public static Operand EmitSwapScalar(ArmEmitterContext context, Operand target, int reg, bool doubleWidth) + { + // Index into 0, 0 into index. This swap happens at the start of an A32 scalar op if required. + int index = reg & (doubleWidth ? 1 : 3); + if (index == 0) return target; + + if (doubleWidth) + { + int shuffleMask = 1; // Swap top and bottom. (b0 = 1, b1 = 0) + return context.AddIntrinsic(Intrinsic.X86Shufpd, target, target, Const(shuffleMask)); + } + else + { + int shuffleMask = (3 << 6) | (2 << 4) | (1 << 2) | index; // Swap index and 0. (others remain) + shuffleMask &= ~(3 << (index * 2)); + + return context.AddIntrinsic(Intrinsic.X86Shufps, target, target, Const(shuffleMask)); + } + } + + // Vector Operand Templates + + public static void EmitVectorUnaryOpSimd32(ArmEmitterContext context, Func1I vectorFunc) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand m = GetVecA32(op.Qm); + Operand d = GetVecA32(op.Qd); + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + m = EmitMoveDoubleWordToSide(context, m, op.Vm, op.Vd); + } + + Operand res = vectorFunc(m); + + if (!op.Q) // Register insert. + { + res = EmitDoubleWordInsert(context, d, res, op.Vd); + } + + context.Copy(d, res); + } + + public static void EmitVectorUnaryOpF32(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + + EmitVectorUnaryOpSimd32(context, (m) => context.AddIntrinsic(inst, m)); + } + + public static void EmitVectorBinaryOpSimd32(ArmEmitterContext context, Func2I vectorFunc, int side = -1) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand n = GetVecA32(op.Qn); + Operand m = GetVecA32(op.Qm); + Operand d = GetVecA32(op.Qd); + + if (side == -1) + { + side = op.Vd; + } + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + n = EmitMoveDoubleWordToSide(context, n, op.Vn, side); + m = EmitMoveDoubleWordToSide(context, m, op.Vm, side); + } + + Operand res = vectorFunc(n, m); + + if (!op.Q) // Register insert. + { + if (side != op.Vd) + { + res = EmitMoveDoubleWordToSide(context, res, side, op.Vd); + } + res = EmitDoubleWordInsert(context, d, res, op.Vd); + } + + context.Copy(d, res); + } + + public static void EmitVectorBinaryOpF32(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(inst, n, m)); + } + + public static void EmitVectorTernaryOpSimd32(ArmEmitterContext context, Func3I vectorFunc) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand n = GetVecA32(op.Qn); + Operand m = GetVecA32(op.Qm); + Operand d = GetVecA32(op.Qd); + Operand initialD = d; + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + n = EmitMoveDoubleWordToSide(context, n, op.Vn, op.Vd); + m = EmitMoveDoubleWordToSide(context, m, op.Vm, op.Vd); + } + + Operand res = vectorFunc(d, n, m); + + if (!op.Q) // Register insert. + { + res = EmitDoubleWordInsert(context, initialD, res, op.Vd); + } + + context.Copy(initialD, res); + } + + public static void EmitVectorTernaryOpF32(ArmEmitterContext context, Intrinsic inst32pt1, Intrinsic inst64pt1, Intrinsic inst32pt2, Intrinsic inst64pt2) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Intrinsic inst1 = (op.Size & 1) != 0 ? inst64pt1 : inst32pt1; + Intrinsic inst2 = (op.Size & 1) != 0 ? inst64pt2 : inst32pt2; + + EmitVectorTernaryOpSimd32(context, (d, n, m) => + { + Operand res = context.AddIntrinsic(inst1, n, m); + return res = context.AddIntrinsic(inst2, d, res); + }); + } + + public static void EmitVectorTernaryOpF32(ArmEmitterContext context, Intrinsic inst32) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Debug.Assert((op.Size & 1) == 0); + + EmitVectorTernaryOpSimd32(context, (d, n, m) => + { + return context.AddIntrinsic(inst32, d, n, m); + }); + } + + public static void EmitScalarUnaryOpSimd32(ArmEmitterContext context, Func1I scalarFunc) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + int shift = doubleSize ? 1 : 2; + Operand m = GetVecA32(op.Vm >> shift); + Operand d = GetVecA32(op.Vd >> shift); + + m = EmitSwapScalar(context, m, op.Vm, doubleSize); + + Operand res = scalarFunc(m); + + // Insert scalar into vector. + res = EmitScalarInsert(context, d, res, op.Vd, doubleSize); + + context.Copy(d, res); + } + + public static void EmitScalarUnaryOpF32(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + + EmitScalarUnaryOpSimd32(context, (m) => (inst == 0) ? m : context.AddIntrinsic(inst, m)); + } + + public static void EmitScalarBinaryOpSimd32(ArmEmitterContext context, Func2I scalarFunc) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + int shift = doubleSize ? 1 : 2; + Operand n = GetVecA32(op.Vn >> shift); + Operand m = GetVecA32(op.Vm >> shift); + Operand d = GetVecA32(op.Vd >> shift); + + n = EmitSwapScalar(context, n, op.Vn, doubleSize); + m = EmitSwapScalar(context, m, op.Vm, doubleSize); + + Operand res = scalarFunc(n, m); + + // Insert scalar into vector. + res = EmitScalarInsert(context, d, res, op.Vd, doubleSize); + + context.Copy(d, res); + } + + public static void EmitScalarBinaryOpF32(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + + EmitScalarBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(inst, n, m)); + } + + public static void EmitScalarTernaryOpSimd32(ArmEmitterContext context, Func3I scalarFunc) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + int shift = doubleSize ? 1 : 2; + Operand n = GetVecA32(op.Vn >> shift); + Operand m = GetVecA32(op.Vm >> shift); + Operand d = GetVecA32(op.Vd >> shift); + Operand initialD = d; + + n = EmitSwapScalar(context, n, op.Vn, doubleSize); + m = EmitSwapScalar(context, m, op.Vm, doubleSize); + d = EmitSwapScalar(context, d, op.Vd, doubleSize); + + Operand res = scalarFunc(d, n, m); + + // Insert scalar into vector. + res = EmitScalarInsert(context, initialD, res, op.Vd, doubleSize); + + context.Copy(initialD, res); + } + + public static void EmitScalarTernaryOpF32(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + + Intrinsic inst = doubleSize ? inst64 : inst32; + + EmitScalarTernaryOpSimd32(context, (d, n, m) => + { + return context.AddIntrinsic(inst, d, n, m); + }); + } + + public static void EmitScalarTernaryOpF32( + ArmEmitterContext context, + Intrinsic inst32pt1, + Intrinsic inst64pt1, + Intrinsic inst32pt2, + Intrinsic inst64pt2, + bool isNegD = false) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + + Intrinsic inst1 = doubleSize ? inst64pt1 : inst32pt1; + Intrinsic inst2 = doubleSize ? inst64pt2 : inst32pt2; + + EmitScalarTernaryOpSimd32(context, (d, n, m) => + { + Operand res = context.AddIntrinsic(inst1, n, m); + + if (isNegD) + { + Operand mask = doubleSize + ? X86GetScalar(context, -0d) + : X86GetScalar(context, -0f); + + d = doubleSize + ? context.AddIntrinsic(Intrinsic.X86Xorpd, mask, d) + : context.AddIntrinsic(Intrinsic.X86Xorps, mask, d); + } + + return context.AddIntrinsic(inst2, d, res); + }); + } + + // By Scalar + + public static void EmitVectorByScalarOpSimd32(ArmEmitterContext context, Func2I vectorFunc) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + Operand n = GetVecA32(op.Qn); + Operand d = GetVecA32(op.Qd); + + int index = op.Vm & 3; + int dupeMask = (index << 6) | (index << 4) | (index << 2) | index; + Operand m = GetVecA32(op.Vm >> 2); + m = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(dupeMask)); + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + n = EmitMoveDoubleWordToSide(context, n, op.Vn, op.Vd); + } + + Operand res = vectorFunc(n, m); + + if (!op.Q) // Register insert. + { + res = EmitDoubleWordInsert(context, d, res, op.Vd); + } + + context.Copy(d, res); + } + + public static void EmitVectorByScalarOpF32(ArmEmitterContext context, Intrinsic inst32, Intrinsic inst64) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + Intrinsic inst = (op.Size & 1) != 0 ? inst64 : inst32; + EmitVectorByScalarOpSimd32(context, (n, m) => context.AddIntrinsic(inst, n, m)); + } + + public static void EmitVectorsByScalarOpSimd32(ArmEmitterContext context, Func3I vectorFunc) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + Operand n = GetVecA32(op.Qn); + Operand d = GetVecA32(op.Qd); + Operand initialD = d; + + int index = op.Vm & 3; + int dupeMask = (index << 6) | (index << 4) | (index << 2) | index; + Operand m = GetVecA32(op.Vm >> 2); + m = context.AddIntrinsic(Intrinsic.X86Shufps, m, m, Const(dupeMask)); + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + n = EmitMoveDoubleWordToSide(context, n, op.Vn, op.Vd); + } + + Operand res = vectorFunc(d, n, m); + + if (!op.Q) // Register insert. + { + res = EmitDoubleWordInsert(context, initialD, res, op.Vd); + } + + context.Copy(initialD, res); + } + + public static void EmitVectorsByScalarOpF32(ArmEmitterContext context, Intrinsic inst32pt1, Intrinsic inst64pt1, Intrinsic inst32pt2, Intrinsic inst64pt2) + { + OpCode32SimdRegElem op = (OpCode32SimdRegElem)context.CurrOp; + + Intrinsic inst1 = (op.Size & 1) != 0 ? inst64pt1 : inst32pt1; + Intrinsic inst2 = (op.Size & 1) != 0 ? inst64pt2 : inst32pt2; + + EmitVectorsByScalarOpSimd32(context, (d, n, m) => + { + Operand res = context.AddIntrinsic(inst1, n, m); + return res = context.AddIntrinsic(inst2, d, res); + }); + } + + // Pairwise + + public static void EmitSse2VectorPairwiseOpF32(ArmEmitterContext context, Intrinsic inst32) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitVectorBinaryOpSimd32(context, (n, m) => + { + Operand unpck = context.AddIntrinsic(Intrinsic.X86Unpcklps, n, m); + + Operand part0 = unpck; + Operand part1 = context.AddIntrinsic(Intrinsic.X86Movhlps, unpck, unpck); + + return context.AddIntrinsic(inst32, part0, part1); + }, 0); + } + + public static void EmitSsse3VectorPairwiseOp32(ArmEmitterContext context, Intrinsic[] inst) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + EmitVectorBinaryOpSimd32(context, (n, m) => + { + if (op.RegisterSize == RegisterSize.Simd64) + { + Operand zeroEvenMask = X86GetElements(context, ZeroMask, EvenMasks[op.Size]); + Operand zeroOddMask = X86GetElements(context, ZeroMask, OddMasks[op.Size]); + + Operand mN = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, n, m); // m:n + + Operand left = context.AddIntrinsic(Intrinsic.X86Pshufb, mN, zeroEvenMask); // 0:even from m:n + Operand right = context.AddIntrinsic(Intrinsic.X86Pshufb, mN, zeroOddMask); // 0:odd from m:n + + return context.AddIntrinsic(inst[op.Size], left, right); + } + else if (op.Size < 3) + { + Operand oddEvenMask = X86GetElements(context, OddMasks[op.Size], EvenMasks[op.Size]); + + Operand oddEvenN = context.AddIntrinsic(Intrinsic.X86Pshufb, n, oddEvenMask); // odd:even from n + Operand oddEvenM = context.AddIntrinsic(Intrinsic.X86Pshufb, m, oddEvenMask); // odd:even from m + + Operand left = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, oddEvenN, oddEvenM); + Operand right = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, oddEvenN, oddEvenM); + + return context.AddIntrinsic(inst[op.Size], left, right); + } + else + { + Operand left = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, n, m); + Operand right = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, n, m); + + return context.AddIntrinsic(inst[3], left, right); + } + }, 0); + } + + // Generic Functions + + public static Operand EmitSoftFloatCallDefaultFpscr(ArmEmitterContext context, string name, params Operand[] callArgs) + { + IOpCodeSimd op = (IOpCodeSimd)context.CurrOp; + + MethodInfo info = (op.Size & 1) == 0 + ? typeof(SoftFloat32).GetMethod(name) + : typeof(SoftFloat64).GetMethod(name); + + Array.Resize(ref callArgs, callArgs.Length + 1); + callArgs[callArgs.Length - 1] = Const(1); + + context.ExitArmFpMode(); + context.StoreToContext(); + Operand res = context.Call(info, callArgs); + context.LoadFromContext(); + context.EnterArmFpMode(); + + return res; + } + + public static Operand EmitVectorExtractSx32(ArmEmitterContext context, int reg, int index, int size) + { + return EmitVectorExtract32(context, reg, index, size, true); + } + + public static Operand EmitVectorExtractZx32(ArmEmitterContext context, int reg, int index, int size) + { + return EmitVectorExtract32(context, reg, index, size, false); + } + + public static Operand EmitVectorExtract32(ArmEmitterContext context, int reg, int index, int size, bool signed) + { + ThrowIfInvalid(index, size); + + Operand res = default; + + switch (size) + { + case 0: + res = context.VectorExtract8(GetVec(reg), index); + break; + + case 1: + res = context.VectorExtract16(GetVec(reg), index); + break; + + case 2: + res = context.VectorExtract(OperandType.I32, GetVec(reg), index); + break; + + case 3: + res = context.VectorExtract(OperandType.I64, GetVec(reg), index); + break; + } + + if (signed) + { + switch (size) + { + case 0: res = context.SignExtend8(OperandType.I32, res); break; + case 1: res = context.SignExtend16(OperandType.I32, res); break; + } + } + else + { + switch (size) + { + case 0: res = context.ZeroExtend8(OperandType.I32, res); break; + case 1: res = context.ZeroExtend16(OperandType.I32, res); break; + } + } + + return res; + } + + public static Operand EmitPolynomialMultiply(ArmEmitterContext context, Operand op1, Operand op2, int eSize) + { + Debug.Assert(eSize <= 32); + + Operand result = eSize == 32 ? Const(0L) : Const(0); + + if (eSize == 32) + { + op1 = context.ZeroExtend32(OperandType.I64, op1); + op2 = context.ZeroExtend32(OperandType.I64, op2); + } + + for (int i = 0; i < eSize; i++) + { + Operand mask = context.BitwiseAnd(op1, Const(op1.Type, 1L << i)); + + result = context.BitwiseExclusiveOr(result, context.Multiply(op2, mask)); + } + + return result; + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdHelper32Arm64.cs b/src/ARMeilleure/Instructions/InstEmitSimdHelper32Arm64.cs new file mode 100644 index 00000000..98236be6 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdHelper32Arm64.cs @@ -0,0 +1,366 @@ + +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + using Func1I = Func; + using Func2I = Func; + using Func3I = Func; + + static class InstEmitSimdHelper32Arm64 + { + // Intrinsic Helpers + + public static Operand EmitMoveDoubleWordToSide(ArmEmitterContext context, Operand input, int originalV, int targetV) + { + Debug.Assert(input.Type == OperandType.V128); + + int originalSide = originalV & 1; + int targetSide = targetV & 1; + + if (originalSide == targetSide) + { + return input; + } + + Intrinsic vType = Intrinsic.Arm64VDWord | Intrinsic.Arm64V128; + + if (targetSide == 1) + { + return context.AddIntrinsic(Intrinsic.Arm64DupVe | vType, input, Const(OperandType.I32, 0)); // Low to high. + } + else + { + return context.AddIntrinsic(Intrinsic.Arm64DupVe | vType, input, Const(OperandType.I32, 1)); // High to low. + } + } + + public static Operand EmitDoubleWordInsert(ArmEmitterContext context, Operand target, Operand value, int targetV) + { + Debug.Assert(target.Type == OperandType.V128 && value.Type == OperandType.V128); + + int targetSide = targetV & 1; + Operand idx = Const(targetSide); + + return context.AddIntrinsic(Intrinsic.Arm64InsVe | Intrinsic.Arm64VDWord, target, idx, value, idx); + } + + public static Operand EmitScalarInsert(ArmEmitterContext context, Operand target, Operand value, int reg, bool doubleWidth) + { + Debug.Assert(target.Type == OperandType.V128 && value.Type == OperandType.V128); + + // Insert from index 0 in value to index in target. + int index = reg & (doubleWidth ? 1 : 3); + + if (doubleWidth) + { + return context.AddIntrinsic(Intrinsic.Arm64InsVe | Intrinsic.Arm64VDWord, target, Const(index), value, Const(0)); + } + else + { + return context.AddIntrinsic(Intrinsic.Arm64InsVe | Intrinsic.Arm64VWord, target, Const(index), value, Const(0)); + } + } + + public static Operand EmitExtractScalar(ArmEmitterContext context, Operand target, int reg, bool doubleWidth) + { + int index = reg & (doubleWidth ? 1 : 3); + if (index == 0) return target; // Element is already at index 0, so just return the vector directly. + + if (doubleWidth) + { + return context.AddIntrinsic(Intrinsic.Arm64DupSe | Intrinsic.Arm64VDWord, target, Const(1)); // Extract high (index 1). + } + else + { + return context.AddIntrinsic(Intrinsic.Arm64DupSe | Intrinsic.Arm64VWord, target, Const(index)); // Extract element at index. + } + } + + // Vector Operand Templates + + public static void EmitVectorUnaryOpSimd32(ArmEmitterContext context, Func1I vectorFunc) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand m = GetVecA32(op.Qm); + Operand d = GetVecA32(op.Qd); + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + m = EmitMoveDoubleWordToSide(context, m, op.Vm, op.Vd); + } + + Operand res = vectorFunc(m); + + if (!op.Q) // Register insert. + { + res = EmitDoubleWordInsert(context, d, res, op.Vd); + } + + context.Copy(d, res); + } + + public static void EmitVectorUnaryOpF32(ArmEmitterContext context, Intrinsic inst) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + inst |= ((op.Size & 1) != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + EmitVectorUnaryOpSimd32(context, (m) => context.AddIntrinsic(inst, m)); + } + + public static void EmitVectorBinaryOpSimd32(ArmEmitterContext context, Func2I vectorFunc, int side = -1) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand n = GetVecA32(op.Qn); + Operand m = GetVecA32(op.Qm); + Operand d = GetVecA32(op.Qd); + + if (side == -1) + { + side = op.Vd; + } + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + n = EmitMoveDoubleWordToSide(context, n, op.Vn, side); + m = EmitMoveDoubleWordToSide(context, m, op.Vm, side); + } + + Operand res = vectorFunc(n, m); + + if (!op.Q) // Register insert. + { + if (side != op.Vd) + { + res = EmitMoveDoubleWordToSide(context, res, side, op.Vd); + } + res = EmitDoubleWordInsert(context, d, res, op.Vd); + } + + context.Copy(d, res); + } + + public static void EmitVectorBinaryOpF32(ArmEmitterContext context, Intrinsic inst) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + inst |= ((op.Size & 1) != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(inst, n, m)); + } + + public static void EmitVectorTernaryOpSimd32(ArmEmitterContext context, Func3I vectorFunc) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + Operand n = GetVecA32(op.Qn); + Operand m = GetVecA32(op.Qm); + Operand d = GetVecA32(op.Qd); + Operand initialD = d; + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + n = EmitMoveDoubleWordToSide(context, n, op.Vn, op.Vd); + m = EmitMoveDoubleWordToSide(context, m, op.Vm, op.Vd); + } + + Operand res = vectorFunc(d, n, m); + + if (!op.Q) // Register insert. + { + res = EmitDoubleWordInsert(context, initialD, res, op.Vd); + } + + context.Copy(initialD, res); + } + + public static void EmitVectorTernaryOpF32(ArmEmitterContext context, Intrinsic inst) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + inst |= ((op.Size & 1) != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + EmitVectorTernaryOpSimd32(context, (d, n, m) => context.AddIntrinsic(inst, d, n, m)); + } + + public static void EmitScalarUnaryOpSimd32(ArmEmitterContext context, Func1I scalarFunc) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + int shift = doubleSize ? 1 : 2; + Operand m = GetVecA32(op.Vm >> shift); + Operand d = GetVecA32(op.Vd >> shift); + + m = EmitExtractScalar(context, m, op.Vm, doubleSize); + + Operand res = scalarFunc(m); + + // Insert scalar into vector. + res = EmitScalarInsert(context, d, res, op.Vd, doubleSize); + + context.Copy(d, res); + } + + public static void EmitScalarUnaryOpF32(ArmEmitterContext context, Intrinsic inst) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + inst |= ((op.Size & 1) != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + EmitScalarUnaryOpSimd32(context, (m) => (inst == 0) ? m : context.AddIntrinsic(inst, m)); + } + + public static void EmitScalarBinaryOpSimd32(ArmEmitterContext context, Func2I scalarFunc) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + int shift = doubleSize ? 1 : 2; + Operand n = GetVecA32(op.Vn >> shift); + Operand m = GetVecA32(op.Vm >> shift); + Operand d = GetVecA32(op.Vd >> shift); + + n = EmitExtractScalar(context, n, op.Vn, doubleSize); + m = EmitExtractScalar(context, m, op.Vm, doubleSize); + + Operand res = scalarFunc(n, m); + + // Insert scalar into vector. + res = EmitScalarInsert(context, d, res, op.Vd, doubleSize); + + context.Copy(d, res); + } + + public static void EmitScalarBinaryOpF32(ArmEmitterContext context, Intrinsic inst) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + inst |= ((op.Size & 1) != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + EmitScalarBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(inst, n, m)); + } + + public static void EmitScalarTernaryOpSimd32(ArmEmitterContext context, Func3I scalarFunc) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + bool doubleSize = (op.Size & 1) != 0; + int shift = doubleSize ? 1 : 2; + Operand n = GetVecA32(op.Vn >> shift); + Operand m = GetVecA32(op.Vm >> shift); + Operand d = GetVecA32(op.Vd >> shift); + Operand initialD = d; + + n = EmitExtractScalar(context, n, op.Vn, doubleSize); + m = EmitExtractScalar(context, m, op.Vm, doubleSize); + d = EmitExtractScalar(context, d, op.Vd, doubleSize); + + Operand res = scalarFunc(d, n, m); + + // Insert scalar into vector. + res = EmitScalarInsert(context, initialD, res, op.Vd, doubleSize); + + context.Copy(initialD, res); + } + + public static void EmitScalarTernaryOpF32(ArmEmitterContext context, Intrinsic inst) + { + OpCode32SimdRegS op = (OpCode32SimdRegS)context.CurrOp; + + inst |= ((op.Size & 1) != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + EmitScalarTernaryOpSimd32(context, (d, n, m) => context.AddIntrinsic(inst, d, n, m)); + } + + // Pairwise + + public static void EmitVectorPairwiseOpF32(ArmEmitterContext context, Intrinsic inst32) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + inst32 |= Intrinsic.Arm64V64 | Intrinsic.Arm64VFloat; + EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(inst32, n, m), 0); + } + + public static void EmitVcmpOrVcmpe(ArmEmitterContext context, bool signalNaNs) + { + OpCode32SimdS op = (OpCode32SimdS)context.CurrOp; + + bool cmpWithZero = (op.Opc & 2) != 0; + + Intrinsic inst = signalNaNs ? Intrinsic.Arm64FcmpeS : Intrinsic.Arm64FcmpS; + inst |= ((op.Size & 1) != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + + bool doubleSize = (op.Size & 1) != 0; + int shift = doubleSize ? 1 : 2; + Operand n = GetVecA32(op.Vd >> shift); + Operand m = GetVecA32(op.Vm >> shift); + + n = EmitExtractScalar(context, n, op.Vd, doubleSize); + m = cmpWithZero ? Const(0) : EmitExtractScalar(context, m, op.Vm, doubleSize); + + Operand nzcv = context.AddIntrinsicInt(inst, n, m); + + Operand one = Const(1); + + SetFpFlag(context, FPState.VFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(28)), one)); + SetFpFlag(context, FPState.CFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(29)), one)); + SetFpFlag(context, FPState.ZFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(30)), one)); + SetFpFlag(context, FPState.NFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(31)), one)); + } + + public static void EmitCmpOpF32(ArmEmitterContext context, CmpCondition cond, bool zero) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + int sizeF = op.Size & 1; + + Intrinsic inst; + if (zero) + { + inst = cond switch + { + CmpCondition.Equal => Intrinsic.Arm64FcmeqVz, + CmpCondition.GreaterThan => Intrinsic.Arm64FcmgtVz, + CmpCondition.GreaterThanOrEqual => Intrinsic.Arm64FcmgeVz, + CmpCondition.LessThan => Intrinsic.Arm64FcmltVz, + CmpCondition.LessThanOrEqual => Intrinsic.Arm64FcmleVz, + _ => throw new InvalidOperationException() + }; + } + else { + inst = cond switch + { + CmpCondition.Equal => Intrinsic.Arm64FcmeqV, + CmpCondition.GreaterThan => Intrinsic.Arm64FcmgtV, + CmpCondition.GreaterThanOrEqual => Intrinsic.Arm64FcmgeV, + _ => throw new InvalidOperationException() + }; + } + + inst |= (sizeF != 0 ? Intrinsic.Arm64VDouble : Intrinsic.Arm64VFloat) | Intrinsic.Arm64V128; + + if (zero) + { + EmitVectorUnaryOpSimd32(context, (m) => + { + return context.AddIntrinsic(inst, m); + }); + } + else + { + EmitVectorBinaryOpSimd32(context, (n, m) => + { + return context.AddIntrinsic(inst, n, m); + }); + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Instructions/InstEmitSimdHelperArm64.cs b/src/ARMeilleure/Instructions/InstEmitSimdHelperArm64.cs new file mode 100644 index 00000000..f0d242ae --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdHelperArm64.cs @@ -0,0 +1,720 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static class InstEmitSimdHelperArm64 + { + public static void EmitScalarUnaryOpF(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n)); + } + + public static void EmitScalarUnaryOpFFromGp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n)); + } + + public static void EmitScalarUnaryOpFToGp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdCvt op = (OpCodeSimdCvt)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + SetIntOrZR(context, op.Rd, op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt (inst, n) + : context.AddIntrinsicLong(inst, n)); + } + + public static void EmitScalarBinaryOpF(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, m)); + } + + public static void EmitScalarBinaryOpFByElem(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, m, Const(op.Index))); + } + + public static void EmitScalarTernaryOpF(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + Operand a = GetVec(op.Ra); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, a, n, m)); + } + + public static void EmitScalarTernaryOpFRdByElem(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(d, context.AddIntrinsic(inst, d, n, m, Const(op.Index))); + } + + public static void EmitScalarUnaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n)); + } + + public static void EmitScalarBinaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, m)); + } + + public static void EmitScalarBinaryOpRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, d, n)); + } + + public static void EmitScalarTernaryOpRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + context.Copy(d, context.AddIntrinsic(inst, d, n, m)); + } + + public static void EmitScalarShiftBinaryOp(ArmEmitterContext context, Intrinsic inst, int shift) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, Const(shift))); + } + + public static void EmitScalarShiftTernaryOpRd(ArmEmitterContext context, Intrinsic inst, int shift) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, d, n, Const(shift))); + } + + public static void EmitScalarSaturatingShiftTernaryOpRd(ArmEmitterContext context, Intrinsic inst, int shift) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, d, n, Const(shift))); + + context.SetPendingQcFlagSync(); + } + + public static void EmitScalarSaturatingUnaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + Operand result = context.AddIntrinsic(inst, n); + + context.Copy(GetVec(op.Rd), result); + + context.SetPendingQcFlagSync(); + } + + public static void EmitScalarSaturatingBinaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + Operand result = context.AddIntrinsic(inst, n, m); + + context.Copy(GetVec(op.Rd), result); + + context.SetPendingQcFlagSync(); + } + + public static void EmitScalarSaturatingBinaryOpRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + Operand result = context.AddIntrinsic(inst, d, n); + + context.Copy(GetVec(op.Rd), result); + + context.SetPendingQcFlagSync(); + } + + public static void EmitScalarConvertBinaryOpF(ArmEmitterContext context, Intrinsic inst, int fBits) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, Const(fBits))); + } + + public static void EmitScalarConvertBinaryOpFFromGp(ArmEmitterContext context, Intrinsic inst, int fBits) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, Const(fBits))); + } + + public static void EmitScalarConvertBinaryOpFToGp(ArmEmitterContext context, Intrinsic inst, int fBits) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + SetIntOrZR(context, op.Rd, op.RegisterSize == RegisterSize.Int32 + ? context.AddIntrinsicInt (inst, n, Const(fBits)) + : context.AddIntrinsicLong(inst, n, Const(fBits))); + } + + public static void EmitVectorUnaryOpF(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n)); + } + + public static void EmitVectorBinaryOpF(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, m)); + } + + public static void EmitVectorBinaryOpFRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, d, n)); + } + + public static void EmitVectorBinaryOpFByElem(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, m, Const(op.Index))); + } + + public static void EmitVectorTernaryOpFRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(d, context.AddIntrinsic(inst, d, n, m)); + } + + public static void EmitVectorTernaryOpFRdByElem(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdRegElemF op = (OpCodeSimdRegElemF)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(d, context.AddIntrinsic(inst, d, n, m, Const(op.Index))); + } + + public static void EmitVectorUnaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n)); + } + + public static void EmitVectorBinaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, m)); + } + + public static void EmitVectorBinaryOpRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, d, n)); + } + + public static void EmitVectorBinaryOpByElem(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, m, Const(op.Index))); + } + + public static void EmitVectorTernaryOpRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(d, context.AddIntrinsic(inst, d, n, m)); + } + + public static void EmitVectorTernaryOpRdByElem(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(d, context.AddIntrinsic(inst, d, n, m, Const(op.Index))); + } + + public static void EmitVectorShiftBinaryOp(ArmEmitterContext context, Intrinsic inst, int shift) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, Const(shift))); + } + + public static void EmitVectorShiftTernaryOpRd(ArmEmitterContext context, Intrinsic inst, int shift) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, d, n, Const(shift))); + } + + public static void EmitVectorSaturatingShiftTernaryOpRd(ArmEmitterContext context, Intrinsic inst, int shift) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, d, n, Const(shift))); + + context.SetPendingQcFlagSync(); + } + + public static void EmitVectorSaturatingUnaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + Operand result = context.AddIntrinsic(inst, n); + + context.Copy(GetVec(op.Rd), result); + + context.SetPendingQcFlagSync(); + } + + public static void EmitVectorSaturatingBinaryOp(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + Operand result = context.AddIntrinsic(inst, n, m); + + context.Copy(GetVec(op.Rd), result); + + context.SetPendingQcFlagSync(); + } + + public static void EmitVectorSaturatingBinaryOpRd(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + Operand result = context.AddIntrinsic(inst, d, n); + + context.Copy(GetVec(op.Rd), result); + + context.SetPendingQcFlagSync(); + } + + public static void EmitVectorSaturatingBinaryOpByElem(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdRegElem op = (OpCodeSimdRegElem)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + inst |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + Operand result = context.AddIntrinsic(inst, n, m, Const(op.Index)); + + context.Copy(GetVec(op.Rd), result); + + context.SetPendingQcFlagSync(); + } + + public static void EmitVectorConvertBinaryOpF(ArmEmitterContext context, Intrinsic inst, int fBits) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, n, Const(fBits))); + } + + public static void EmitVectorLookupTable(ArmEmitterContext context, Intrinsic inst) + { + OpCodeSimdTbl op = (OpCodeSimdTbl)context.CurrOp; + + Operand[] operands = new Operand[op.Size + 1]; + + operands[op.Size] = GetVec(op.Rm); + + for (int index = 0; index < op.Size; index++) + { + operands[index] = GetVec((op.Rn + index) & 0x1F); + } + + if (op.RegisterSize == RegisterSize.Simd128) + { + inst |= Intrinsic.Arm64V128; + } + + context.Copy(GetVec(op.Rd), context.AddIntrinsic(inst, operands)); + } + + public static void EmitFcmpOrFcmpe(ArmEmitterContext context, bool signalNaNs) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + bool cmpWithZero = !(op is OpCodeSimdFcond) ? op.Bit3 : false; + + Intrinsic inst = signalNaNs ? Intrinsic.Arm64FcmpeS : Intrinsic.Arm64FcmpS; + + if ((op.Size & 1) != 0) + { + inst |= Intrinsic.Arm64VDouble; + } + + Operand n = GetVec(op.Rn); + Operand m = cmpWithZero ? Const(0) : GetVec(op.Rm); + + Operand nzcv = context.AddIntrinsicInt(inst, n, m); + + Operand one = Const(1); + + SetFlag(context, PState.VFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(28)), one)); + SetFlag(context, PState.CFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(29)), one)); + SetFlag(context, PState.ZFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(30)), one)); + SetFlag(context, PState.NFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const(31)), one)); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Instructions/InstEmitSimdLogical.cs b/src/ARMeilleure/Instructions/InstEmitSimdLogical.cs new file mode 100644 index 00000000..2bf531e6 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdLogical.cs @@ -0,0 +1,612 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void And_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64AndV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pand, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => context.BitwiseAnd(op1, op2)); + } + } + + public static void Bic_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64BicV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pandn, m, n); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => + { + return context.BitwiseAnd(op1, context.BitwiseNot(op2)); + }); + } + } + + public static void Bic_Vi(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + OpCodeSimdImm op = (OpCodeSimdImm)context.CurrOp; + + int eSize = 8 << op.Size; + + Operand d = GetVec(op.Rd); + Operand imm = eSize switch { + 16 => X86GetAllElements(context, (short)~op.Immediate), + 32 => X86GetAllElements(context, (int)~op.Immediate), + _ => throw new InvalidOperationException($"Invalid element size {eSize}.") + }; + + Operand res = context.AddIntrinsic(Intrinsic.X86Pand, d, imm); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorImmBinaryOp(context, (op1, op2) => + { + return context.BitwiseAnd(op1, context.BitwiseNot(op2)); + }); + } + } + + public static void Bif_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64BifV); + } + else + { + EmitBifBit(context, notRm: true); + } + } + + public static void Bit_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64BitV); + } + else + { + EmitBifBit(context, notRm: false); + } + } + + private static void EmitBifBit(ArmEmitterContext context, bool notRm) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseSse2) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pxor, n, d); + + if (notRm) + { + res = context.AddIntrinsic(Intrinsic.X86Pandn, m, res); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Pand, m, res); + } + + res = context.AddIntrinsic(Intrinsic.X86Pxor, d, res); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + Operand res = context.VectorZero(); + + int elems = op.RegisterSize == RegisterSize.Simd128 ? 2 : 1; + + for (int index = 0; index < elems; index++) + { + Operand d = EmitVectorExtractZx(context, op.Rd, index, 3); + Operand n = EmitVectorExtractZx(context, op.Rn, index, 3); + Operand m = EmitVectorExtractZx(context, op.Rm, index, 3); + + if (notRm) + { + m = context.BitwiseNot(m); + } + + Operand e = context.BitwiseExclusiveOr(d, n); + + e = context.BitwiseAnd(e, m); + e = context.BitwiseExclusiveOr(e, d); + + res = EmitVectorInsert(context, res, e, index, 3); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Bsl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorTernaryOpRd(context, Intrinsic.Arm64BslV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pxor, n, m); + + res = context.AddIntrinsic(Intrinsic.X86Pand, res, d); + res = context.AddIntrinsic(Intrinsic.X86Pxor, res, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + EmitVectorTernaryOpZx(context, (op1, op2, op3) => + { + return context.BitwiseExclusiveOr( + context.BitwiseAnd(op1, + context.BitwiseExclusiveOr(op2, op3)), op3); + }); + } + } + + public static void Eor_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64EorV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pxor, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => context.BitwiseExclusiveOr(op1, op2)); + } + } + + public static void Not_V(ArmEmitterContext context) + { + if (Optimizations.UseAvx512Ortho) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand res = context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, n, Const(~0b10101010)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else if (Optimizations.UseSse2) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand mask = X86GetAllElements(context, -1L); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pandn, n, mask); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorUnaryOpZx(context, (op1) => context.BitwiseNot(op1)); + } + } + + public static void Orn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64OrnV); + } + else if (Optimizations.UseAvx512Ortho) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, m, Const(0b11001100 | ~0b10101010)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else if (Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand mask = X86GetAllElements(context, -1L); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pandn, m, mask); + + res = context.AddIntrinsic(Intrinsic.X86Por, res, n); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => + { + return context.BitwiseOr(op1, context.BitwiseNot(op2)); + }); + } + } + + public static void Orr_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64OrrV); + } + else if (Optimizations.UseSse2) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Operand res = context.AddIntrinsic(Intrinsic.X86Por, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorBinaryOpZx(context, (op1, op2) => context.BitwiseOr(op1, op2)); + } + } + + public static void Orr_Vi(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + OpCodeSimdImm op = (OpCodeSimdImm)context.CurrOp; + + int eSize = 8 << op.Size; + + Operand d = GetVec(op.Rd); + Operand imm = eSize switch { + 16 => X86GetAllElements(context, (short)op.Immediate), + 32 => X86GetAllElements(context, (int)op.Immediate), + _ => throw new InvalidOperationException($"Invalid element size {eSize}.") + }; + + Operand res = context.AddIntrinsic(Intrinsic.X86Por, d, imm); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorImmBinaryOp(context, (op1, op2) => context.BitwiseOr(op1, op2)); + } + } + + public static void Rbit_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + if (Optimizations.UseGfni) + { + const long bitMatrix = + (0b10000000L << 56) | + (0b01000000L << 48) | + (0b00100000L << 40) | + (0b00010000L << 32) | + (0b00001000L << 24) | + (0b00000100L << 16) | + (0b00000010L << 8) | + (0b00000001L << 0); + + Operand vBitMatrix = X86GetAllElements(context, bitMatrix); + + Operand res = context.AddIntrinsic(Intrinsic.X86Gf2p8affineqb, GetVec(op.Rn), vBitMatrix, Const(0)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand res = context.VectorZero(); + int elems = op.RegisterSize == RegisterSize.Simd128 ? 16 : 8; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, 0); + + Operand de = EmitReverseBits8Op(context, ne); + + res = EmitVectorInsert(context, res, de, index, 0); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static Operand EmitReverseBits8Op(ArmEmitterContext context, Operand op) + { + Debug.Assert(op.Type == OperandType.I64); + + Operand val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(op, Const(0xaaul)), Const(1)), + context.ShiftLeft (context.BitwiseAnd(op, Const(0x55ul)), Const(1))); + + val = context.BitwiseOr(context.ShiftRightUI(context.BitwiseAnd(val, Const(0xccul)), Const(2)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x33ul)), Const(2))); + + return context.BitwiseOr(context.ShiftRightUI(val, Const(4)), + context.ShiftLeft (context.BitwiseAnd(val, Const(0x0ful)), Const(4))); + } + + public static void Rev16_V(ArmEmitterContext context) + { + if (Optimizations.UseSsse3) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + const long maskE0 = 06L << 56 | 07L << 48 | 04L << 40 | 05L << 32 | 02L << 24 | 03L << 16 | 00L << 8 | 01L << 0; + const long maskE1 = 14L << 56 | 15L << 48 | 12L << 40 | 13L << 32 | 10L << 24 | 11L << 16 | 08L << 8 | 09L << 0; + + Operand mask = X86GetScalar(context, maskE0); + + mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mask); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitRev_V(context, containerSize: 1); + } + } + + public static void Rev32_V(ArmEmitterContext context) + { + if (Optimizations.UseSsse3) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand mask; + + if (op.Size == 0) + { + const long maskE0 = 04L << 56 | 05L << 48 | 06L << 40 | 07L << 32 | 00L << 24 | 01L << 16 | 02L << 8 | 03L << 0; + const long maskE1 = 12L << 56 | 13L << 48 | 14L << 40 | 15L << 32 | 08L << 24 | 09L << 16 | 10L << 8 | 11L << 0; + + mask = X86GetScalar(context, maskE0); + + mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3); + } + else /* if (op.Size == 1) */ + { + const long maskE0 = 05L << 56 | 04L << 48 | 07L << 40 | 06L << 32 | 01L << 24 | 00L << 16 | 03L << 8 | 02L << 0; + const long maskE1 = 13L << 56 | 12L << 48 | 15L << 40 | 14L << 32 | 09L << 24 | 08L << 16 | 11L << 8 | 10L << 0; + + mask = X86GetScalar(context, maskE0); + + mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3); + } + + Operand res = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mask); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitRev_V(context, containerSize: 2); + } + } + + public static void Rev64_V(ArmEmitterContext context) + { + if (Optimizations.UseSsse3) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetVec(op.Rn); + + Operand mask; + + if (op.Size == 0) + { + const long maskE0 = 00L << 56 | 01L << 48 | 02L << 40 | 03L << 32 | 04L << 24 | 05L << 16 | 06L << 8 | 07L << 0; + const long maskE1 = 08L << 56 | 09L << 48 | 10L << 40 | 11L << 32 | 12L << 24 | 13L << 16 | 14L << 8 | 15L << 0; + + mask = X86GetScalar(context, maskE0); + + mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3); + } + else if (op.Size == 1) + { + const long maskE0 = 01L << 56 | 00L << 48 | 03L << 40 | 02L << 32 | 05L << 24 | 04L << 16 | 07L << 8 | 06L << 0; + const long maskE1 = 09L << 56 | 08L << 48 | 11L << 40 | 10L << 32 | 13L << 24 | 12L << 16 | 15L << 8 | 14L << 0; + + mask = X86GetScalar(context, maskE0); + + mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3); + } + else /* if (op.Size == 2) */ + { + const long maskE0 = 03L << 56 | 02L << 48 | 01L << 40 | 00L << 32 | 07L << 24 | 06L << 16 | 05L << 8 | 04L << 0; + const long maskE1 = 11L << 56 | 10L << 48 | 09L << 40 | 08L << 32 | 15L << 24 | 14L << 16 | 13L << 8 | 12L << 0; + + mask = X86GetScalar(context, maskE0); + + mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3); + } + + Operand res = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mask); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitRev_V(context, containerSize: 3); + } + } + + private static void EmitRev_V(ArmEmitterContext context, int containerSize) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + int containerMask = (1 << (containerSize - op.Size)) - 1; + + for (int index = 0; index < elems; index++) + { + int revIndex = index ^ containerMask; + + Operand ne = EmitVectorExtractZx(context, op.Rn, revIndex, op.Size); + + res = EmitVectorInsert(context, res, ne, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdLogical32.cs b/src/ARMeilleure/Instructions/InstEmitSimdLogical32.cs new file mode 100644 index 00000000..68ef4ed1 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdLogical32.cs @@ -0,0 +1,266 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper32; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void Vand_I(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64AndV | Intrinsic.Arm64V128, n, m)); + } + else if (Optimizations.UseSse2) + { + EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.X86Pand, n, m)); + } + else + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.BitwiseAnd(op1, op2)); + } + } + + public static void Vbic_I(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64BicV | Intrinsic.Arm64V128, n, m)); + } + else if (Optimizations.UseSse2) + { + EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.X86Pandn, m, n)); + } + else + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.BitwiseAnd(op1, context.BitwiseNot(op2))); + } + } + + public static void Vbic_II(ArmEmitterContext context) + { + OpCode32SimdImm op = (OpCode32SimdImm)context.CurrOp; + + long immediate = op.Immediate; + + // Replicate fields to fill the 64-bits, if size is < 64-bits. + switch (op.Size) + { + case 0: immediate *= 0x0101010101010101L; break; + case 1: immediate *= 0x0001000100010001L; break; + case 2: immediate *= 0x0000000100000001L; break; + } + + Operand imm = Const(immediate); + Operand res = GetVecA32(op.Qd); + + if (op.Q) + { + for (int elem = 0; elem < 2; elem++) + { + Operand de = EmitVectorExtractZx(context, op.Qd, elem, 3); + + res = EmitVectorInsert(context, res, context.BitwiseAnd(de, context.BitwiseNot(imm)), elem, 3); + } + } + else + { + Operand de = EmitVectorExtractZx(context, op.Qd, op.Vd & 1, 3); + + res = EmitVectorInsert(context, res, context.BitwiseAnd(de, context.BitwiseNot(imm)), op.Vd & 1, 3); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Vbif(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorTernaryOpSimd32(context, (d, n, m) => context.AddIntrinsic(Intrinsic.Arm64BifV | Intrinsic.Arm64V128, d, n, m)); + } + else + { + EmitBifBit(context, true); + } + } + + public static void Vbit(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorTernaryOpSimd32(context, (d, n, m) => context.AddIntrinsic(Intrinsic.Arm64BitV | Intrinsic.Arm64V128, d, n, m)); + } + else + { + EmitBifBit(context, false); + } + } + + public static void Vbsl(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorTernaryOpSimd32(context, (d, n, m) => context.AddIntrinsic(Intrinsic.Arm64BslV | Intrinsic.Arm64V128, d, n, m)); + } + else if (Optimizations.UseSse2) + { + EmitVectorTernaryOpSimd32(context, (d, n, m) => + { + Operand res = context.AddIntrinsic(Intrinsic.X86Pxor, n, m); + res = context.AddIntrinsic(Intrinsic.X86Pand, res, d); + return context.AddIntrinsic(Intrinsic.X86Pxor, res, m); + }); + } + else + { + EmitVectorTernaryOpZx32(context, (op1, op2, op3) => + { + return context.BitwiseExclusiveOr( + context.BitwiseAnd(op1, + context.BitwiseExclusiveOr(op2, op3)), op3); + }); + } + } + + public static void Veor_I(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64EorV | Intrinsic.Arm64V128, n, m)); + } + else if (Optimizations.UseSse2) + { + EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.X86Pxor, n, m)); + } + else + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.BitwiseExclusiveOr(op1, op2)); + } + } + + public static void Vorn_I(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64OrnV | Intrinsic.Arm64V128, n, m)); + } + else if (Optimizations.UseAvx512Ortho) + { + EmitVectorBinaryOpSimd32(context, (n, m) => + { + return context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, m, Const(0b11001100 | ~0b10101010)); + }); + } + else if (Optimizations.UseSse2) + { + Operand mask = context.VectorOne(); + + EmitVectorBinaryOpSimd32(context, (n, m) => + { + m = context.AddIntrinsic(Intrinsic.X86Pandn, m, mask); + return context.AddIntrinsic(Intrinsic.X86Por, n, m); + }); + } + else + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.BitwiseOr(op1, context.BitwiseNot(op2))); + } + } + + public static void Vorr_I(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64OrrV | Intrinsic.Arm64V128, n, m)); + } + else if (Optimizations.UseSse2) + { + EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.X86Por, n, m)); + } + else + { + EmitVectorBinaryOpZx32(context, (op1, op2) => context.BitwiseOr(op1, op2)); + } + } + + public static void Vorr_II(ArmEmitterContext context) + { + OpCode32SimdImm op = (OpCode32SimdImm)context.CurrOp; + + long immediate = op.Immediate; + + // Replicate fields to fill the 64-bits, if size is < 64-bits. + switch (op.Size) + { + case 0: immediate *= 0x0101010101010101L; break; + case 1: immediate *= 0x0001000100010001L; break; + case 2: immediate *= 0x0000000100000001L; break; + } + + Operand imm = Const(immediate); + Operand res = GetVecA32(op.Qd); + + if (op.Q) + { + for (int elem = 0; elem < 2; elem++) + { + Operand de = EmitVectorExtractZx(context, op.Qd, elem, 3); + + res = EmitVectorInsert(context, res, context.BitwiseOr(de, imm), elem, 3); + } + } + else + { + Operand de = EmitVectorExtractZx(context, op.Qd, op.Vd & 1, 3); + + res = EmitVectorInsert(context, res, context.BitwiseOr(de, imm), op.Vd & 1, 3); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Vtst(ArmEmitterContext context) + { + EmitVectorBinaryOpZx32(context, (op1, op2) => + { + Operand isZero = context.ICompareEqual(context.BitwiseAnd(op1, op2), Const(0)); + return context.ConditionalSelect(isZero, Const(0), Const(-1)); + }); + } + + private static void EmitBifBit(ArmEmitterContext context, bool notRm) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (Optimizations.UseSse2) + { + EmitVectorTernaryOpSimd32(context, (d, n, m) => + { + Operand res = context.AddIntrinsic(Intrinsic.X86Pxor, n, d); + res = context.AddIntrinsic((notRm) ? Intrinsic.X86Pandn : Intrinsic.X86Pand, m, res); + return context.AddIntrinsic(Intrinsic.X86Pxor, d, res); + }); + } + else + { + EmitVectorTernaryOpZx32(context, (d, n, m) => + { + if (notRm) + { + m = context.BitwiseNot(m); + } + return context.BitwiseExclusiveOr( + context.BitwiseAnd(m, + context.BitwiseExclusiveOr(d, n)), d); + }); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdMemory.cs b/src/ARMeilleure/Instructions/InstEmitSimdMemory.cs new file mode 100644 index 00000000..9b19872a --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdMemory.cs @@ -0,0 +1,160 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System.Diagnostics; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitMemoryHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + public static void Ld__Vms(ArmEmitterContext context) + { + EmitSimdMemMs(context, isLoad: true); + } + + public static void Ld__Vss(ArmEmitterContext context) + { + EmitSimdMemSs(context, isLoad: true); + } + + public static void St__Vms(ArmEmitterContext context) + { + EmitSimdMemMs(context, isLoad: false); + } + + public static void St__Vss(ArmEmitterContext context) + { + EmitSimdMemSs(context, isLoad: false); + } + + private static void EmitSimdMemMs(ArmEmitterContext context, bool isLoad) + { + OpCodeSimdMemMs op = (OpCodeSimdMemMs)context.CurrOp; + + Operand n = GetIntOrSP(context, op.Rn); + + long offset = 0; + + for (int rep = 0; rep < op.Reps; rep++) + for (int elem = 0; elem < op.Elems; elem++) + for (int sElem = 0; sElem < op.SElems; sElem++) + { + int rtt = (op.Rt + rep + sElem) & 0x1f; + + Operand tt = GetVec(rtt); + + Operand address = context.Add(n, Const(offset)); + + if (isLoad) + { + EmitLoadSimd(context, address, tt, rtt, elem, op.Size); + + if (op.RegisterSize == RegisterSize.Simd64 && elem == op.Elems - 1) + { + context.Copy(tt, context.VectorZeroUpper64(tt)); + } + } + else + { + EmitStoreSimd(context, address, rtt, elem, op.Size); + } + + offset += 1 << op.Size; + } + + if (op.WBack) + { + EmitSimdMemWBack(context, offset); + } + } + + private static void EmitSimdMemSs(ArmEmitterContext context, bool isLoad) + { + OpCodeSimdMemSs op = (OpCodeSimdMemSs)context.CurrOp; + + Operand n = GetIntOrSP(context, op.Rn); + + long offset = 0; + + if (op.Replicate) + { + // Only loads uses the replicate mode. + Debug.Assert(isLoad, "Replicate mode is not valid for stores."); + + int elems = op.GetBytesCount() >> op.Size; + + for (int sElem = 0; sElem < op.SElems; sElem++) + { + int rt = (op.Rt + sElem) & 0x1f; + + Operand t = GetVec(rt); + + Operand address = context.Add(n, Const(offset)); + + for (int index = 0; index < elems; index++) + { + EmitLoadSimd(context, address, t, rt, index, op.Size); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + context.Copy(t, context.VectorZeroUpper64(t)); + } + + offset += 1 << op.Size; + } + } + else + { + for (int sElem = 0; sElem < op.SElems; sElem++) + { + int rt = (op.Rt + sElem) & 0x1f; + + Operand t = GetVec(rt); + + Operand address = context.Add(n, Const(offset)); + + if (isLoad) + { + EmitLoadSimd(context, address, t, rt, op.Index, op.Size); + } + else + { + EmitStoreSimd(context, address, rt, op.Index, op.Size); + } + + offset += 1 << op.Size; + } + } + + if (op.WBack) + { + EmitSimdMemWBack(context, offset); + } + } + + private static void EmitSimdMemWBack(ArmEmitterContext context, long offset) + { + OpCodeMemReg op = (OpCodeMemReg)context.CurrOp; + + Operand n = GetIntOrSP(context, op.Rn); + Operand m; + + if (op.Rm != RegisterAlias.Zr) + { + m = GetIntOrZR(context, op.Rm); + } + else + { + m = Const(offset); + } + + context.Copy(n, context.Add(n, m)); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Instructions/InstEmitSimdMemory32.cs b/src/ARMeilleure/Instructions/InstEmitSimdMemory32.cs new file mode 100644 index 00000000..b774bd06 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdMemory32.cs @@ -0,0 +1,352 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitMemoryHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void Vld1(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 1, true); + } + + public static void Vld2(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 2, true); + } + + public static void Vld3(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 3, true); + } + + public static void Vld4(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 4, true); + } + + public static void Vst1(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 1, false); + } + + public static void Vst2(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 2, false); + } + + public static void Vst3(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 3, false); + } + + public static void Vst4(ArmEmitterContext context) + { + EmitVStoreOrLoadN(context, 4, false); + } + + public static void EmitVStoreOrLoadN(ArmEmitterContext context, int count, bool load) + { + if (context.CurrOp is OpCode32SimdMemSingle) + { + OpCode32SimdMemSingle op = (OpCode32SimdMemSingle)context.CurrOp; + + int eBytes = 1 << op.Size; + + Operand n = context.Copy(GetIntA32(context, op.Rn)); + + // TODO: Check alignment. + int offset = 0; + int d = op.Vd; + + for (int i = 0; i < count; i++) + { + // Accesses an element from a double simd register. + Operand address = context.Add(n, Const(offset)); + if (eBytes == 8) + { + if (load) + { + EmitDVectorLoad(context, address, d); + } + else + { + EmitDVectorStore(context, address, d); + } + } + else + { + int index = ((d & 1) << (3 - op.Size)) + op.Index; + if (load) + { + if (op.Replicate) + { + var regs = (count > 1) ? 1 : op.Increment; + for (int reg = 0; reg < regs; reg++) + { + int dreg = reg + d; + int rIndex = ((dreg & 1) << (3 - op.Size)); + int limit = rIndex + (1 << (3 - op.Size)); + + while (rIndex < limit) + { + EmitLoadSimd(context, address, GetVecA32(dreg >> 1), dreg >> 1, rIndex++, op.Size); + } + } + } + else + { + EmitLoadSimd(context, address, GetVecA32(d >> 1), d >> 1, index, op.Size); + } + } + else + { + EmitStoreSimd(context, address, d >> 1, index, op.Size); + } + } + offset += eBytes; + d += op.Increment; + } + + if (op.WBack) + { + if (op.RegisterIndex) + { + Operand m = GetIntA32(context, op.Rm); + SetIntA32(context, op.Rn, context.Add(n, m)); + } + else + { + SetIntA32(context, op.Rn, context.Add(n, Const(count * eBytes))); + } + } + } + else + { + OpCode32SimdMemPair op = (OpCode32SimdMemPair)context.CurrOp; + + int increment = count > 1 ? op.Increment : 1; + int eBytes = 1 << op.Size; + + Operand n = context.Copy(GetIntA32(context, op.Rn)); + int offset = 0; + int d = op.Vd; + + for (int reg = 0; reg < op.Regs; reg++) + { + for (int elem = 0; elem < op.Elems; elem++) + { + int elemD = d + reg; + for (int i = 0; i < count; i++) + { + // Accesses an element from a double simd register, + // add ebytes for each element. + Operand address = context.Add(n, Const(offset)); + int index = ((elemD & 1) << (3 - op.Size)) + elem; + if (eBytes == 8) + { + if (load) + { + EmitDVectorLoad(context, address, elemD); + } + else + { + EmitDVectorStore(context, address, elemD); + } + } + else + { + if (load) + { + EmitLoadSimd(context, address, GetVecA32(elemD >> 1), elemD >> 1, index, op.Size); + } + else + { + EmitStoreSimd(context, address, elemD >> 1, index, op.Size); + } + } + + offset += eBytes; + elemD += increment; + } + } + } + + if (op.WBack) + { + if (op.RegisterIndex) + { + Operand m = GetIntA32(context, op.Rm); + SetIntA32(context, op.Rn, context.Add(n, m)); + } + else + { + SetIntA32(context, op.Rn, context.Add(n, Const(count * 8 * op.Regs))); + } + } + } + } + + public static void Vldm(ArmEmitterContext context) + { + OpCode32SimdMemMult op = (OpCode32SimdMemMult)context.CurrOp; + + Operand n = context.Copy(GetIntA32(context, op.Rn)); + + Operand baseAddress = context.Add(n, Const(op.Offset)); + + bool writeBack = op.PostOffset != 0; + + if (writeBack) + { + SetIntA32(context, op.Rn, context.Add(n, Const(op.PostOffset))); + } + + int range = op.RegisterRange; + + int sReg = (op.DoubleWidth) ? (op.Vd << 1) : op.Vd; + int offset = 0; + int byteSize = 4; + + for (int num = 0; num < range; num++, sReg++) + { + Operand address = context.Add(baseAddress, Const(offset)); + Operand vec = GetVecA32(sReg >> 2); + + EmitLoadSimd(context, address, vec, sReg >> 2, sReg & 3, WordSizeLog2); + offset += byteSize; + } + } + + public static void Vstm(ArmEmitterContext context) + { + OpCode32SimdMemMult op = (OpCode32SimdMemMult)context.CurrOp; + + Operand n = context.Copy(GetIntA32(context, op.Rn)); + + Operand baseAddress = context.Add(n, Const(op.Offset)); + + bool writeBack = op.PostOffset != 0; + + if (writeBack) + { + SetIntA32(context, op.Rn, context.Add(n, Const(op.PostOffset))); + } + + int offset = 0; + + int range = op.RegisterRange; + int sReg = (op.DoubleWidth) ? (op.Vd << 1) : op.Vd; + int byteSize = 4; + + for (int num = 0; num < range; num++, sReg++) + { + Operand address = context.Add(baseAddress, Const(offset)); + + EmitStoreSimd(context, address, sReg >> 2, sReg & 3, WordSizeLog2); + + offset += byteSize; + } + } + + public static void Vldr(ArmEmitterContext context) + { + EmitVLoadOrStore(context, AccessType.Load); + } + + public static void Vstr(ArmEmitterContext context) + { + EmitVLoadOrStore(context, AccessType.Store); + } + + private static void EmitDVectorStore(ArmEmitterContext context, Operand address, int vecD) + { + int vecQ = vecD >> 1; + int vecSElem = (vecD & 1) << 1; + Operand lblBigEndian = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBigEndian, GetFlag(PState.EFlag)); + + EmitStoreSimd(context, address, vecQ, vecSElem, WordSizeLog2); + EmitStoreSimd(context, context.Add(address, Const(4)), vecQ, vecSElem | 1, WordSizeLog2); + + context.Branch(lblEnd); + + context.MarkLabel(lblBigEndian); + + EmitStoreSimd(context, address, vecQ, vecSElem | 1, WordSizeLog2); + EmitStoreSimd(context, context.Add(address, Const(4)), vecQ, vecSElem, WordSizeLog2); + + context.MarkLabel(lblEnd); + } + + private static void EmitDVectorLoad(ArmEmitterContext context, Operand address, int vecD) + { + int vecQ = vecD >> 1; + int vecSElem = (vecD & 1) << 1; + Operand vec = GetVecA32(vecQ); + + Operand lblBigEndian = Label(); + Operand lblEnd = Label(); + + context.BranchIfTrue(lblBigEndian, GetFlag(PState.EFlag)); + + EmitLoadSimd(context, address, vec, vecQ, vecSElem, WordSizeLog2); + EmitLoadSimd(context, context.Add(address, Const(4)), vec, vecQ, vecSElem | 1, WordSizeLog2); + + context.Branch(lblEnd); + + context.MarkLabel(lblBigEndian); + + EmitLoadSimd(context, address, vec, vecQ, vecSElem | 1, WordSizeLog2); + EmitLoadSimd(context, context.Add(address, Const(4)), vec, vecQ, vecSElem, WordSizeLog2); + + context.MarkLabel(lblEnd); + } + + private static void EmitVLoadOrStore(ArmEmitterContext context, AccessType accType) + { + OpCode32SimdMemImm op = (OpCode32SimdMemImm)context.CurrOp; + + Operand n = context.Copy(GetIntA32(context, op.Rn)); + Operand m = GetMemM(context, setCarry: false); + + Operand address = op.Add + ? context.Add(n, m) + : context.Subtract(n, m); + + int size = op.Size; + + if ((accType & AccessType.Load) != 0) + { + if (size == DWordSizeLog2) + { + EmitDVectorLoad(context, address, op.Vd); + } + else + { + Operand vec = GetVecA32(op.Vd >> 2); + EmitLoadSimd(context, address, vec, op.Vd >> 2, (op.Vd & 3) << (2 - size), size); + } + } + else + { + if (size == DWordSizeLog2) + { + EmitDVectorStore(context, address, op.Vd); + } + else + { + EmitStoreSimd(context, address, op.Vd >> 2, (op.Vd & 3) << (2 - size), size); + } + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdMove.cs b/src/ARMeilleure/Instructions/InstEmitSimdMove.cs new file mode 100644 index 00000000..b58a32f6 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdMove.cs @@ -0,0 +1,850 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System.Collections.Generic; +using System.Reflection; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { +#region "Masks" + private static readonly long[] _masksE0_Uzp = new long[] + { + 13L << 56 | 09L << 48 | 05L << 40 | 01L << 32 | 12L << 24 | 08L << 16 | 04L << 8 | 00L << 0, + 11L << 56 | 10L << 48 | 03L << 40 | 02L << 32 | 09L << 24 | 08L << 16 | 01L << 8 | 00L << 0 + }; + + private static readonly long[] _masksE1_Uzp = new long[] + { + 15L << 56 | 11L << 48 | 07L << 40 | 03L << 32 | 14L << 24 | 10L << 16 | 06L << 8 | 02L << 0, + 15L << 56 | 14L << 48 | 07L << 40 | 06L << 32 | 13L << 24 | 12L << 16 | 05L << 8 | 04L << 0 + }; +#endregion + + public static void Dup_Gp(ArmEmitterContext context) + { + OpCodeSimdIns op = (OpCodeSimdIns)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + + if (Optimizations.UseSse2) + { + switch (op.Size) + { + case 0: n = context.ZeroExtend8 (n.Type, n); n = context.Multiply(n, Const(n.Type, 0x01010101)); break; + case 1: n = context.ZeroExtend16(n.Type, n); n = context.Multiply(n, Const(n.Type, 0x00010001)); break; + case 2: n = context.ZeroExtend32(n.Type, n); break; + } + + Operand res = context.VectorInsert(context.VectorZero(), n, 0); + + if (op.Size < 3) + { + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.AddIntrinsic(Intrinsic.X86Shufps, res, res, Const(0xf0)); + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Shufps, res, res, Const(0)); + } + } + else + { + res = context.AddIntrinsic(Intrinsic.X86Movlhps, res, res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + res = EmitVectorInsert(context, res, n, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Dup_S(ArmEmitterContext context) + { + OpCodeSimdIns op = (OpCodeSimdIns)context.CurrOp; + + Operand ne = EmitVectorExtractZx(context, op.Rn, op.DstIndex, op.Size); + + context.Copy(GetVec(op.Rd), EmitVectorInsert(context, context.VectorZero(), ne, 0, op.Size)); + } + + public static void Dup_V(ArmEmitterContext context) + { + OpCodeSimdIns op = (OpCodeSimdIns)context.CurrOp; + + if (Optimizations.UseSse2) + { + Operand res = GetVec(op.Rn); + + if (op.Size == 0) + { + if (op.DstIndex != 0) + { + res = context.AddIntrinsic(Intrinsic.X86Psrldq, res, Const(op.DstIndex)); + } + + res = context.AddIntrinsic(Intrinsic.X86Punpcklbw, res, res); + res = context.AddIntrinsic(Intrinsic.X86Punpcklwd, res, res); + res = context.AddIntrinsic(Intrinsic.X86Shufps, res, res, Const(0)); + } + else if (op.Size == 1) + { + if (op.DstIndex != 0) + { + res = context.AddIntrinsic(Intrinsic.X86Psrldq, res, Const(op.DstIndex * 2)); + } + + res = context.AddIntrinsic(Intrinsic.X86Punpcklwd, res, res); + res = context.AddIntrinsic(Intrinsic.X86Shufps, res, res, Const(0)); + } + else if (op.Size == 2) + { + int mask = op.DstIndex * 0b01010101; + + res = context.AddIntrinsic(Intrinsic.X86Shufps, res, res, Const(mask)); + } + else if (op.DstIndex == 0 && op.RegisterSize != RegisterSize.Simd64) + { + res = context.AddIntrinsic(Intrinsic.X86Movlhps, res, res); + } + else if (op.DstIndex == 1) + { + res = context.AddIntrinsic(Intrinsic.X86Movhlps, res, res); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand ne = EmitVectorExtractZx(context, op.Rn, op.DstIndex, op.Size); + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + res = EmitVectorInsert(context, res, ne, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Ext_V(ArmEmitterContext context) + { + OpCodeSimdExt op = (OpCodeSimdExt)context.CurrOp; + + if (Optimizations.UseSse2) + { + Operand nShifted = GetVec(op.Rn); + + if (op.RegisterSize == RegisterSize.Simd64) + { + nShifted = context.VectorZeroUpper64(nShifted); + } + + nShifted = context.AddIntrinsic(Intrinsic.X86Psrldq, nShifted, Const(op.Imm4)); + + Operand mShifted = GetVec(op.Rm); + + mShifted = context.AddIntrinsic(Intrinsic.X86Pslldq, mShifted, Const(op.GetBytesCount() - op.Imm4)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + mShifted = context.VectorZeroUpper64(mShifted); + } + + Operand res = context.AddIntrinsic(Intrinsic.X86Por, nShifted, mShifted); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand res = context.VectorZero(); + + int bytes = op.GetBytesCount(); + + int position = op.Imm4 & (bytes - 1); + + for (int index = 0; index < bytes; index++) + { + int reg = op.Imm4 + index < bytes ? op.Rn : op.Rm; + + Operand e = EmitVectorExtractZx(context, reg, position, 0); + + position = (position + 1) & (bytes - 1); + + res = EmitVectorInsert(context, res, e, index, 0); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Fcsel_S(ArmEmitterContext context) + { + OpCodeSimdFcond op = (OpCodeSimdFcond)context.CurrOp; + + Operand lblTrue = Label(); + Operand lblEnd = Label(); + + Operand isTrue = InstEmitFlowHelper.GetCondTrue(context, op.Cond); + + context.BranchIfTrue(lblTrue, isTrue); + + OperandType type = op.Size == 0 ? OperandType.FP32 : OperandType.FP64; + + Operand me = context.VectorExtract(type, GetVec(op.Rm), 0); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), me, 0)); + + context.Branch(lblEnd); + + context.MarkLabel(lblTrue); + + Operand ne = context.VectorExtract(type, GetVec(op.Rn), 0); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), ne, 0)); + + context.MarkLabel(lblEnd); + } + + public static void Fmov_Ftoi(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand ne = EmitVectorExtractZx(context, op.Rn, 0, op.Size + 2); + + SetIntOrZR(context, op.Rd, ne); + } + + public static void Fmov_Ftoi1(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand ne = EmitVectorExtractZx(context, op.Rn, 1, 3); + + SetIntOrZR(context, op.Rd, ne); + } + + public static void Fmov_Itof(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand n = GetIntOrZR(context, op.Rn); + + context.Copy(GetVec(op.Rd), EmitVectorInsert(context, context.VectorZero(), n, 0, op.Size + 2)); + } + + public static void Fmov_Itof1(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetIntOrZR(context, op.Rn); + + context.Copy(d, EmitVectorInsert(context, d, n, 1, 3)); + } + + public static void Fmov_S(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + OperandType type = op.Size == 0 ? OperandType.FP32 : OperandType.FP64; + + Operand ne = context.VectorExtract(type, GetVec(op.Rn), 0); + + context.Copy(GetVec(op.Rd), context.VectorInsert(context.VectorZero(), ne, 0)); + } + + public static void Fmov_Si(ArmEmitterContext context) + { + OpCodeSimdFmov op = (OpCodeSimdFmov)context.CurrOp; + + if (Optimizations.UseSse2) + { + if (op.Size == 0) + { + context.Copy(GetVec(op.Rd), X86GetScalar(context, (int)op.Immediate)); + } + else + { + context.Copy(GetVec(op.Rd), X86GetScalar(context, op.Immediate)); + } + } + else + { + Operand e = Const(op.Immediate); + + Operand res = context.VectorZero(); + + res = EmitVectorInsert(context, res, e, 0, op.Size + 2); + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Fmov_Vi(ArmEmitterContext context) + { + OpCodeSimdImm op = (OpCodeSimdImm)context.CurrOp; + + if (Optimizations.UseSse2) + { + if (op.RegisterSize == RegisterSize.Simd128) + { + context.Copy(GetVec(op.Rd), X86GetAllElements(context, op.Immediate)); + } + else + { + context.Copy(GetVec(op.Rd), X86GetScalar(context, op.Immediate)); + } + } + else + { + Operand e = Const(op.Immediate); + + Operand res = context.VectorZero(); + + int elems = op.RegisterSize == RegisterSize.Simd128 ? 2 : 1; + + for (int index = 0; index < elems; index++) + { + res = EmitVectorInsert(context, res, e, index, 3); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + public static void Ins_Gp(ArmEmitterContext context) + { + OpCodeSimdIns op = (OpCodeSimdIns)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand n = GetIntOrZR(context, op.Rn); + + context.Copy(d, EmitVectorInsert(context, d, n, op.DstIndex, op.Size)); + } + + public static void Ins_V(ArmEmitterContext context) + { + OpCodeSimdIns op = (OpCodeSimdIns)context.CurrOp; + + Operand d = GetVec(op.Rd); + Operand ne = EmitVectorExtractZx(context, op.Rn, op.SrcIndex, op.Size); + + context.Copy(d, EmitVectorInsert(context, d, ne, op.DstIndex, op.Size)); + } + + public static void Movi_V(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + EmitSse2VectorMoviMvniOp(context, not: false); + } + else + { + EmitVectorImmUnaryOp(context, (op1) => op1); + } + } + + public static void Mvni_V(ArmEmitterContext context) + { + if (Optimizations.UseSse2) + { + EmitSse2VectorMoviMvniOp(context, not: true); + } + else + { + EmitVectorImmUnaryOp(context, (op1) => context.BitwiseNot(op1)); + } + } + + public static void Smov_S(ArmEmitterContext context) + { + OpCodeSimdIns op = (OpCodeSimdIns)context.CurrOp; + + Operand ne = EmitVectorExtractSx(context, op.Rn, op.DstIndex, op.Size); + + if (op.RegisterSize == RegisterSize.Simd64) + { + ne = context.ZeroExtend32(OperandType.I64, ne); + } + + SetIntOrZR(context, op.Rd, ne); + } + + public static void Tbl_V(ArmEmitterContext context) + { + EmitTableVectorLookup(context, isTbl: true); + } + + public static void Tbx_V(ArmEmitterContext context) + { + EmitTableVectorLookup(context, isTbl: false); + } + + public static void Trn1_V(ArmEmitterContext context) + { + EmitVectorTranspose(context, part: 0); + } + + public static void Trn2_V(ArmEmitterContext context) + { + EmitVectorTranspose(context, part: 1); + } + + public static void Umov_S(ArmEmitterContext context) + { + OpCodeSimdIns op = (OpCodeSimdIns)context.CurrOp; + + Operand ne = EmitVectorExtractZx(context, op.Rn, op.DstIndex, op.Size); + + SetIntOrZR(context, op.Rd, ne); + } + + public static void Uzp1_V(ArmEmitterContext context) + { + EmitVectorUnzip(context, part: 0); + } + + public static void Uzp2_V(ArmEmitterContext context) + { + EmitVectorUnzip(context, part: 1); + } + + public static void Xtn_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + if (Optimizations.UseSsse3) + { + Operand d = GetVec(op.Rd); + + Operand res = context.VectorZeroUpper64(d); + + Operand mask = X86GetAllElements(context, EvenMasks[op.Size]); + + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pshufb, GetVec(op.Rn), mask); + + Intrinsic movInst = op.RegisterSize == RegisterSize.Simd128 + ? Intrinsic.X86Movlhps + : Intrinsic.X86Movhlps; + + res = context.AddIntrinsic(movInst, res, res2); + + context.Copy(d, res); + } + else + { + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size + 1); + + res = EmitVectorInsert(context, res, ne, part + index, op.Size); + } + + context.Copy(d, res); + } + } + + public static void Zip1_V(ArmEmitterContext context) + { + EmitVectorZip(context, part: 0); + } + + public static void Zip2_V(ArmEmitterContext context) + { + EmitVectorZip(context, part: 1); + } + + private static void EmitSse2VectorMoviMvniOp(ArmEmitterContext context, bool not) + { + OpCodeSimdImm op = (OpCodeSimdImm)context.CurrOp; + + long imm = op.Immediate; + + switch (op.Size) + { + case 0: imm *= 0x01010101; break; + case 1: imm *= 0x00010001; break; + } + + if (not) + { + imm = ~imm; + } + + Operand mask; + + if (op.Size < 3) + { + mask = X86GetAllElements(context, (int)imm); + } + else + { + mask = X86GetAllElements(context, imm); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + mask = context.VectorZeroUpper64(mask); + } + + context.Copy(GetVec(op.Rd), mask); + } + + private static void EmitTableVectorLookup(ArmEmitterContext context, bool isTbl) + { + OpCodeSimdTbl op = (OpCodeSimdTbl)context.CurrOp; + + if (Optimizations.UseSsse3) + { + Operand d = GetVec(op.Rd); + Operand m = GetVec(op.Rm); + + Operand res; + + Operand mask = X86GetAllElements(context, 0x0F0F0F0F0F0F0F0FL); + + // Fast path for single register table. + { + Operand n = GetVec(op.Rn); + + Operand mMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, m, mask); + mMask = context.AddIntrinsic(Intrinsic.X86Por, mMask, m); + + res = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mMask); + } + + for (int index = 1; index < op.Size; index++) + { + Operand ni = GetVec((op.Rn + index) & 0x1F); + + Operand idxMask = X86GetAllElements(context, 0x1010101010101010L * index); + + Operand mSubMask = context.AddIntrinsic(Intrinsic.X86Psubb, m, idxMask); + + Operand mMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, mSubMask, mask); + mMask = context.AddIntrinsic(Intrinsic.X86Por, mMask, mSubMask); + + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pshufb, ni, mMask); + + res = context.AddIntrinsic(Intrinsic.X86Por, res, res2); + } + + if (!isTbl) + { + Operand idxMask = X86GetAllElements(context, (0x1010101010101010L * op.Size) - 0x0101010101010101L); + Operand zeroMask = context.VectorZero(); + + Operand mPosMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, m, idxMask); + Operand mNegMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, zeroMask, m); + + Operand mMask = context.AddIntrinsic(Intrinsic.X86Por, mPosMask, mNegMask); + + Operand dMask = context.AddIntrinsic(Intrinsic.X86Pand, d, mMask); + + res = context.AddIntrinsic(Intrinsic.X86Por, res, dMask); + } + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + Operand d = GetVec(op.Rd); + + List args = new List(); + + if (!isTbl) + { + args.Add(d); + } + + args.Add(GetVec(op.Rm)); + + args.Add(Const(op.RegisterSize == RegisterSize.Simd64 ? 8 : 16)); + + for (int index = 0; index < op.Size; index++) + { + args.Add(GetVec((op.Rn + index) & 0x1F)); + } + + MethodInfo info = null; + + if (isTbl) + { + switch (op.Size) + { + case 1: info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl1)); break; + case 2: info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl2)); break; + case 3: info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl3)); break; + case 4: info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl4)); break; + } + } + else + { + switch (op.Size) + { + case 1: info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx1)); break; + case 2: info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx2)); break; + case 3: info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx3)); break; + case 4: info = typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx4)); break; + } + } + + context.Copy(d, context.Call(info, args.ToArray())); + } + } + + private static void EmitVectorTranspose(ArmEmitterContext context, int part) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseSsse3) + { + Operand mask = default; + + if (op.Size < 3) + { + long maskE0 = EvenMasks[op.Size]; + long maskE1 = OddMasks [op.Size]; + + mask = X86GetScalar(context, maskE0); + + mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3); + } + + Operand n = GetVec(op.Rn); + + if (op.Size < 3) + { + n = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mask); + } + + Operand m = GetVec(op.Rm); + + if (op.Size < 3) + { + m = context.AddIntrinsic(Intrinsic.X86Pshufb, m, mask); + } + + Intrinsic punpckInst = part == 0 + ? X86PunpcklInstruction[op.Size] + : X86PunpckhInstruction[op.Size]; + + Operand res = context.AddIntrinsic(punpckInst, n, m); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand res = context.VectorZero(); + + int pairs = op.GetPairsCount() >> op.Size; + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + + Operand ne = EmitVectorExtractZx(context, op.Rn, pairIndex + part, op.Size); + Operand me = EmitVectorExtractZx(context, op.Rm, pairIndex + part, op.Size); + + res = EmitVectorInsert(context, res, ne, pairIndex, op.Size); + res = EmitVectorInsert(context, res, me, pairIndex + 1, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static void EmitVectorUnzip(ArmEmitterContext context, int part) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseSsse3) + { + if (op.RegisterSize == RegisterSize.Simd128) + { + Operand mask = default; + + if (op.Size < 3) + { + long maskE0 = EvenMasks[op.Size]; + long maskE1 = OddMasks [op.Size]; + + mask = X86GetScalar(context, maskE0); + + mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3); + } + + Operand n = GetVec(op.Rn); + + if (op.Size < 3) + { + n = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mask); + } + + Operand m = GetVec(op.Rm); + + if (op.Size < 3) + { + m = context.AddIntrinsic(Intrinsic.X86Pshufb, m, mask); + } + + Intrinsic punpckInst = part == 0 + ? Intrinsic.X86Punpcklqdq + : Intrinsic.X86Punpckhqdq; + + Operand res = context.AddIntrinsic(punpckInst, n, m); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + Intrinsic punpcklInst = X86PunpcklInstruction[op.Size]; + + Operand res = context.AddIntrinsic(punpcklInst, n, m); + + if (op.Size < 2) + { + long maskE0 = _masksE0_Uzp[op.Size]; + long maskE1 = _masksE1_Uzp[op.Size]; + + Operand mask = X86GetScalar(context, maskE0); + + mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3); + + res = context.AddIntrinsic(Intrinsic.X86Pshufb, res, mask); + } + + Intrinsic punpckInst = part == 0 + ? Intrinsic.X86Punpcklqdq + : Intrinsic.X86Punpckhqdq; + + res = context.AddIntrinsic(punpckInst, res, context.VectorZero()); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + Operand res = context.VectorZero(); + + int pairs = op.GetPairsCount() >> op.Size; + + for (int index = 0; index < pairs; index++) + { + int idx = index << 1; + + Operand ne = EmitVectorExtractZx(context, op.Rn, idx + part, op.Size); + Operand me = EmitVectorExtractZx(context, op.Rm, idx + part, op.Size); + + res = EmitVectorInsert(context, res, ne, index, op.Size); + res = EmitVectorInsert(context, res, me, pairs + index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static void EmitVectorZip(ArmEmitterContext context, int part) + { + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + if (Optimizations.UseSse2) + { + Operand n = GetVec(op.Rn); + Operand m = GetVec(op.Rm); + + if (op.RegisterSize == RegisterSize.Simd128) + { + Intrinsic punpckInst = part == 0 + ? X86PunpcklInstruction[op.Size] + : X86PunpckhInstruction[op.Size]; + + Operand res = context.AddIntrinsic(punpckInst, n, m); + + context.Copy(GetVec(op.Rd), res); + } + else + { + Operand res = context.AddIntrinsic(X86PunpcklInstruction[op.Size], n, m); + + Intrinsic punpckInst = part == 0 + ? Intrinsic.X86Punpcklqdq + : Intrinsic.X86Punpckhqdq; + + res = context.AddIntrinsic(punpckInst, res, context.VectorZero()); + + context.Copy(GetVec(op.Rd), res); + } + } + else + { + Operand res = context.VectorZero(); + + int pairs = op.GetPairsCount() >> op.Size; + + int baseIndex = part != 0 ? pairs : 0; + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + + Operand ne = EmitVectorExtractZx(context, op.Rn, baseIndex + index, op.Size); + Operand me = EmitVectorExtractZx(context, op.Rm, baseIndex + index, op.Size); + + res = EmitVectorInsert(context, res, ne, pairIndex, op.Size); + res = EmitVectorInsert(context, res, me, pairIndex + 1, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdMove32.cs b/src/ARMeilleure/Instructions/InstEmitSimdMove32.cs new file mode 100644 index 00000000..b8b91b31 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdMove32.cs @@ -0,0 +1,656 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper32; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + #region "Masks" + // Same as InstEmitSimdMove, as the instructions do the same thing. + private static readonly long[] _masksE0_Uzp = new long[] + { + 13L << 56 | 09L << 48 | 05L << 40 | 01L << 32 | 12L << 24 | 08L << 16 | 04L << 8 | 00L << 0, + 11L << 56 | 10L << 48 | 03L << 40 | 02L << 32 | 09L << 24 | 08L << 16 | 01L << 8 | 00L << 0 + }; + + private static readonly long[] _masksE1_Uzp = new long[] + { + 15L << 56 | 11L << 48 | 07L << 40 | 03L << 32 | 14L << 24 | 10L << 16 | 06L << 8 | 02L << 0, + 15L << 56 | 14L << 48 | 07L << 40 | 06L << 32 | 13L << 24 | 12L << 16 | 05L << 8 | 04L << 0 + }; + #endregion + + public static void Vmov_I(ArmEmitterContext context) + { + EmitVectorImmUnaryOp32(context, (op1) => op1); + } + + public static void Vmvn_I(ArmEmitterContext context) + { + if (Optimizations.UseAvx512Ortho) + { + EmitVectorUnaryOpSimd32(context, (op1) => + { + return context.AddIntrinsic(Intrinsic.X86Vpternlogd, op1, op1, Const(0b01010101)); + }); + } + else if (Optimizations.UseSse2) + { + EmitVectorUnaryOpSimd32(context, (op1) => + { + Operand mask = X86GetAllElements(context, -1L); + return context.AddIntrinsic(Intrinsic.X86Pandn, op1, mask); + }); + } + else + { + EmitVectorUnaryOpZx32(context, (op1) => context.BitwiseNot(op1)); + } + } + + public static void Vmvn_II(ArmEmitterContext context) + { + EmitVectorImmUnaryOp32(context, (op1) => context.BitwiseNot(op1)); + } + + public static void Vmov_GS(ArmEmitterContext context) + { + OpCode32SimdMovGp op = (OpCode32SimdMovGp)context.CurrOp; + + Operand vec = GetVecA32(op.Vn >> 2); + if (op.Op == 1) + { + // To general purpose. + Operand value = context.VectorExtract(OperandType.I32, vec, op.Vn & 0x3); + SetIntA32(context, op.Rt, value); + } + else + { + // From general purpose. + Operand value = GetIntA32(context, op.Rt); + context.Copy(vec, context.VectorInsert(vec, value, op.Vn & 0x3)); + } + } + + public static void Vmov_G1(ArmEmitterContext context) + { + OpCode32SimdMovGpElem op = (OpCode32SimdMovGpElem)context.CurrOp; + + int index = op.Index + ((op.Vd & 1) << (3 - op.Size)); + if (op.Op == 1) + { + // To general purpose. + Operand value = EmitVectorExtract32(context, op.Vd >> 1, index, op.Size, !op.U); + SetIntA32(context, op.Rt, value); + } + else + { + // From general purpose. + Operand vec = GetVecA32(op.Vd >> 1); + Operand value = GetIntA32(context, op.Rt); + context.Copy(vec, EmitVectorInsert(context, vec, value, index, op.Size)); + } + } + + public static void Vmov_G2(ArmEmitterContext context) + { + OpCode32SimdMovGpDouble op = (OpCode32SimdMovGpDouble)context.CurrOp; + + Operand vec = GetVecA32(op.Vm >> 2); + int vm1 = op.Vm + 1; + bool sameOwnerVec = (op.Vm >> 2) == (vm1 >> 2); + Operand vec2 = sameOwnerVec ? vec : GetVecA32(vm1 >> 2); + if (op.Op == 1) + { + // To general purpose. + Operand lowValue = context.VectorExtract(OperandType.I32, vec, op.Vm & 3); + SetIntA32(context, op.Rt, lowValue); + + Operand highValue = context.VectorExtract(OperandType.I32, vec2, vm1 & 3); + SetIntA32(context, op.Rt2, highValue); + } + else + { + // From general purpose. + Operand lowValue = GetIntA32(context, op.Rt); + Operand resultVec = context.VectorInsert(vec, lowValue, op.Vm & 3); + + Operand highValue = GetIntA32(context, op.Rt2); + + if (sameOwnerVec) + { + context.Copy(vec, context.VectorInsert(resultVec, highValue, vm1 & 3)); + } + else + { + context.Copy(vec, resultVec); + context.Copy(vec2, context.VectorInsert(vec2, highValue, vm1 & 3)); + } + } + } + + public static void Vmov_GD(ArmEmitterContext context) + { + OpCode32SimdMovGpDouble op = (OpCode32SimdMovGpDouble)context.CurrOp; + + Operand vec = GetVecA32(op.Vm >> 1); + if (op.Op == 1) + { + // To general purpose. + Operand value = context.VectorExtract(OperandType.I64, vec, op.Vm & 1); + SetIntA32(context, op.Rt, context.ConvertI64ToI32(value)); + SetIntA32(context, op.Rt2, context.ConvertI64ToI32(context.ShiftRightUI(value, Const(32)))); + } + else + { + // From general purpose. + Operand lowValue = GetIntA32(context, op.Rt); + Operand highValue = GetIntA32(context, op.Rt2); + + Operand value = context.BitwiseOr( + context.ZeroExtend32(OperandType.I64, lowValue), + context.ShiftLeft(context.ZeroExtend32(OperandType.I64, highValue), Const(32))); + + context.Copy(vec, context.VectorInsert(vec, value, op.Vm & 1)); + } + } + + public static void Vmovl(ArmEmitterContext context) + { + OpCode32SimdLong op = (OpCode32SimdLong)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, !op.U); + + if (op.Size == 2) + { + if (op.U) + { + me = context.ZeroExtend32(OperandType.I64, me); + } + else + { + me = context.SignExtend32(OperandType.I64, me); + } + } + + res = EmitVectorInsert(context, res, me, index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Vtbl(ArmEmitterContext context) + { + OpCode32SimdTbl op = (OpCode32SimdTbl)context.CurrOp; + + bool extension = op.Opc == 1; + int length = op.Length + 1; + + if (Optimizations.UseSsse3) + { + Operand d = GetVecA32(op.Qd); + Operand m = EmitMoveDoubleWordToSide(context, GetVecA32(op.Qm), op.Vm, 0); + + Operand res; + Operand mask = X86GetAllElements(context, 0x0707070707070707L); + + // Fast path for single register table. + { + Operand n = EmitMoveDoubleWordToSide(context, GetVecA32(op.Qn), op.Vn, 0); + + Operand mMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, m, mask); + mMask = context.AddIntrinsic(Intrinsic.X86Por, mMask, m); + + res = context.AddIntrinsic(Intrinsic.X86Pshufb, n, mMask); + } + + for (int index = 1; index < length; index++) + { + int newVn = (op.Vn + index) & 0x1F; + (int qn, int ind) = GetQuadwordAndSubindex(newVn, op.RegisterSize); + Operand ni = EmitMoveDoubleWordToSide(context, GetVecA32(qn), newVn, 0); + + Operand idxMask = X86GetAllElements(context, 0x0808080808080808L * index); + + Operand mSubMask = context.AddIntrinsic(Intrinsic.X86Psubb, m, idxMask); + + Operand mMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, mSubMask, mask); + mMask = context.AddIntrinsic(Intrinsic.X86Por, mMask, mSubMask); + + Operand res2 = context.AddIntrinsic(Intrinsic.X86Pshufb, ni, mMask); + + res = context.AddIntrinsic(Intrinsic.X86Por, res, res2); + } + + if (extension) + { + Operand idxMask = X86GetAllElements(context, (0x0808080808080808L * length) - 0x0101010101010101L); + Operand zeroMask = context.VectorZero(); + + Operand mPosMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, m, idxMask); + Operand mNegMask = context.AddIntrinsic(Intrinsic.X86Pcmpgtb, zeroMask, m); + + Operand mMask = context.AddIntrinsic(Intrinsic.X86Por, mPosMask, mNegMask); + + Operand dMask = context.AddIntrinsic(Intrinsic.X86Pand, EmitMoveDoubleWordToSide(context, d, op.Vd, 0), mMask); + + res = context.AddIntrinsic(Intrinsic.X86Por, res, dMask); + } + + res = EmitMoveDoubleWordToSide(context, res, 0, op.Vd); + + context.Copy(d, EmitDoubleWordInsert(context, d, res, op.Vd)); + } + else + { + int elems = op.GetBytesCount() >> op.Size; + + (int Qx, int Ix)[] tableTuples = new (int, int)[length]; + for (int i = 0; i < length; i++) + { + tableTuples[i] = GetQuadwordAndSubindex(op.Vn + i, op.RegisterSize); + } + + int byteLength = length * 8; + + Operand res = GetVecA32(op.Qd); + Operand m = GetVecA32(op.Qm); + + for (int index = 0; index < elems; index++) + { + Operand selectedIndex = context.ZeroExtend8(OperandType.I32, context.VectorExtract8(m, index + op.Im)); + + Operand inRange = context.ICompareLess(selectedIndex, Const(byteLength)); + Operand elemRes = default; // Note: This is I64 for ease of calculation. + + // TODO: Branching rather than conditional select. + + // Get indexed byte. + // To simplify (ha) the il, we get bytes from every vector and use a nested conditional select to choose the right result. + // This does have to extract `length` times for every element but certainly not as bad as it could be. + + // Which vector number is the index on. + Operand vecIndex = context.ShiftRightUI(selectedIndex, Const(3)); + // What should we shift by to extract it. + Operand subVecIndexShift = context.ShiftLeft(context.BitwiseAnd(selectedIndex, Const(7)), Const(3)); + + for (int i = 0; i < length; i++) + { + (int qx, int ix) = tableTuples[i]; + // Get the whole vector, we'll get a byte out of it. + Operand lookupResult; + if (qx == op.Qd) + { + // Result contains the current state of the vector. + lookupResult = context.VectorExtract(OperandType.I64, res, ix); + } + else + { + lookupResult = EmitVectorExtract32(context, qx, ix, 3, false); // I64 + } + + lookupResult = context.ShiftRightUI(lookupResult, subVecIndexShift); // Get the relevant byte from this vector. + + if (i == 0) + { + elemRes = lookupResult; // First result is always default. + } + else + { + Operand isThisElem = context.ICompareEqual(vecIndex, Const(i)); + elemRes = context.ConditionalSelect(isThisElem, lookupResult, elemRes); + } + } + + Operand fallback = (extension) ? context.ZeroExtend32(OperandType.I64, EmitVectorExtract32(context, op.Qd, index + op.Id, 0, false)) : Const(0L); + + res = EmitVectorInsert(context, res, context.ConditionalSelect(inRange, elemRes, fallback), index + op.Id, 0); + } + + context.Copy(GetVecA32(op.Qd), res); + } + } + + public static void Vtrn(ArmEmitterContext context) + { + OpCode32SimdCmpZ op = (OpCode32SimdCmpZ)context.CurrOp; + + if (Optimizations.UseSsse3) + { + EmitVectorShuffleOpSimd32(context, (m, d) => + { + Operand mask = default; + + if (op.Size < 3) + { + long maskE0 = EvenMasks[op.Size]; + long maskE1 = OddMasks[op.Size]; + + mask = X86GetScalar(context, maskE0); + + mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3); + } + + if (op.Size < 3) + { + d = context.AddIntrinsic(Intrinsic.X86Pshufb, d, mask); + m = context.AddIntrinsic(Intrinsic.X86Pshufb, m, mask); + } + + Operand resD = context.AddIntrinsic(X86PunpcklInstruction[op.Size], d, m); + Operand resM = context.AddIntrinsic(X86PunpckhInstruction[op.Size], d, m); + + return (resM, resD); + }); + } + else + { + int elems = op.GetBytesCount() >> op.Size; + int pairs = elems >> 1; + + bool overlap = op.Qm == op.Qd; + + Operand resD = GetVecA32(op.Qd); + Operand resM = GetVecA32(op.Qm); + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + Operand d2 = EmitVectorExtract32(context, op.Qd, pairIndex + 1 + op.Id, op.Size, false); + Operand m1 = EmitVectorExtract32(context, op.Qm, pairIndex + op.Im, op.Size, false); + + resD = EmitVectorInsert(context, resD, m1, pairIndex + 1 + op.Id, op.Size); + + if (overlap) + { + resM = resD; + } + + resM = EmitVectorInsert(context, resM, d2, pairIndex + op.Im, op.Size); + + if (overlap) + { + resD = resM; + } + } + + context.Copy(GetVecA32(op.Qd), resD); + if (!overlap) + { + context.Copy(GetVecA32(op.Qm), resM); + } + } + } + + public static void Vzip(ArmEmitterContext context) + { + OpCode32SimdCmpZ op = (OpCode32SimdCmpZ)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + EmitVectorZipUzpOpSimd32(context, Intrinsic.Arm64Zip1V, Intrinsic.Arm64Zip2V); + } + else if (Optimizations.UseSse2) + { + EmitVectorShuffleOpSimd32(context, (m, d) => + { + if (op.RegisterSize == RegisterSize.Simd128) + { + Operand resD = context.AddIntrinsic(X86PunpcklInstruction[op.Size], d, m); + Operand resM = context.AddIntrinsic(X86PunpckhInstruction[op.Size], d, m); + + return (resM, resD); + } + else + { + Operand res = context.AddIntrinsic(X86PunpcklInstruction[op.Size], d, m); + + Operand resD = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, res, context.VectorZero()); + Operand resM = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, res, context.VectorZero()); + return (resM, resD); + } + }); + } + else + { + int elems = op.GetBytesCount() >> op.Size; + int pairs = elems >> 1; + + bool overlap = op.Qm == op.Qd; + + Operand resD = GetVecA32(op.Qd); + Operand resM = GetVecA32(op.Qm); + + for (int index = 0; index < pairs; index++) + { + int pairIndex = index << 1; + Operand dRowD = EmitVectorExtract32(context, op.Qd, index + op.Id, op.Size, false); + Operand mRowD = EmitVectorExtract32(context, op.Qm, index + op.Im, op.Size, false); + + Operand dRowM = EmitVectorExtract32(context, op.Qd, index + op.Id + pairs, op.Size, false); + Operand mRowM = EmitVectorExtract32(context, op.Qm, index + op.Im + pairs, op.Size, false); + + resD = EmitVectorInsert(context, resD, dRowD, pairIndex + op.Id, op.Size); + resD = EmitVectorInsert(context, resD, mRowD, pairIndex + 1 + op.Id, op.Size); + + if (overlap) + { + resM = resD; + } + + resM = EmitVectorInsert(context, resM, dRowM, pairIndex + op.Im, op.Size); + resM = EmitVectorInsert(context, resM, mRowM, pairIndex + 1 + op.Im, op.Size); + + if (overlap) + { + resD = resM; + } + } + + context.Copy(GetVecA32(op.Qd), resD); + if (!overlap) + { + context.Copy(GetVecA32(op.Qm), resM); + } + } + } + + public static void Vuzp(ArmEmitterContext context) + { + OpCode32SimdCmpZ op = (OpCode32SimdCmpZ)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + EmitVectorZipUzpOpSimd32(context, Intrinsic.Arm64Uzp1V, Intrinsic.Arm64Uzp2V); + } + else if (Optimizations.UseSsse3) + { + EmitVectorShuffleOpSimd32(context, (m, d) => + { + if (op.RegisterSize == RegisterSize.Simd128) + { + Operand mask = default; + + if (op.Size < 3) + { + long maskE0 = EvenMasks[op.Size]; + long maskE1 = OddMasks[op.Size]; + + mask = X86GetScalar(context, maskE0); + mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3); + + d = context.AddIntrinsic(Intrinsic.X86Pshufb, d, mask); + m = context.AddIntrinsic(Intrinsic.X86Pshufb, m, mask); + } + + Operand resD = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, d, m); + Operand resM = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, d, m); + + return (resM, resD); + } + else + { + Intrinsic punpcklInst = X86PunpcklInstruction[op.Size]; + + Operand res = context.AddIntrinsic(punpcklInst, d, m); + + if (op.Size < 2) + { + long maskE0 = _masksE0_Uzp[op.Size]; + long maskE1 = _masksE1_Uzp[op.Size]; + + Operand mask = X86GetScalar(context, maskE0); + + mask = EmitVectorInsert(context, mask, Const(maskE1), 1, 3); + + res = context.AddIntrinsic(Intrinsic.X86Pshufb, res, mask); + } + + Operand resD = context.AddIntrinsic(Intrinsic.X86Punpcklqdq, res, context.VectorZero()); + Operand resM = context.AddIntrinsic(Intrinsic.X86Punpckhqdq, res, context.VectorZero()); + + return (resM, resD); + } + }); + } + else + { + int elems = op.GetBytesCount() >> op.Size; + int pairs = elems >> 1; + + bool overlap = op.Qm == op.Qd; + + Operand resD = GetVecA32(op.Qd); + Operand resM = GetVecA32(op.Qm); + + for (int index = 0; index < elems; index++) + { + Operand dIns, mIns; + if (index >= pairs) + { + int pairIndex = index - pairs; + dIns = EmitVectorExtract32(context, op.Qm, (pairIndex << 1) + op.Im, op.Size, false); + mIns = EmitVectorExtract32(context, op.Qm, ((pairIndex << 1) | 1) + op.Im, op.Size, false); + } + else + { + dIns = EmitVectorExtract32(context, op.Qd, (index << 1) + op.Id, op.Size, false); + mIns = EmitVectorExtract32(context, op.Qd, ((index << 1) | 1) + op.Id, op.Size, false); + } + + resD = EmitVectorInsert(context, resD, dIns, index + op.Id, op.Size); + + if (overlap) + { + resM = resD; + } + + resM = EmitVectorInsert(context, resM, mIns, index + op.Im, op.Size); + + if (overlap) + { + resD = resM; + } + } + + context.Copy(GetVecA32(op.Qd), resD); + if (!overlap) + { + context.Copy(GetVecA32(op.Qm), resM); + } + } + } + + private static void EmitVectorZipUzpOpSimd32(ArmEmitterContext context, Intrinsic inst1, Intrinsic inst2) + { + OpCode32SimdCmpZ op = (OpCode32SimdCmpZ)context.CurrOp; + + bool overlap = op.Qm == op.Qd; + + Operand d = GetVecA32(op.Qd); + Operand m = GetVecA32(op.Qm); + + Operand dPart = d; + Operand mPart = m; + + if (!op.Q) // Register swap: move relevant doubleword to destination side. + { + dPart = InstEmitSimdHelper32Arm64.EmitMoveDoubleWordToSide(context, d, op.Vd, 0); + mPart = InstEmitSimdHelper32Arm64.EmitMoveDoubleWordToSide(context, m, op.Vm, 0); + } + + Intrinsic vSize = op.Q ? Intrinsic.Arm64V128 : Intrinsic.Arm64V64; + + vSize |= (Intrinsic)(op.Size << (int)Intrinsic.Arm64VSizeShift); + + Operand resD = context.AddIntrinsic(inst1 | vSize, dPart, mPart); + Operand resM = context.AddIntrinsic(inst2 | vSize, dPart, mPart); + + if (!op.Q) // Register insert. + { + resD = context.AddIntrinsic(Intrinsic.Arm64InsVe | Intrinsic.Arm64VDWord, d, Const(op.Vd & 1), resD, Const(0)); + + if (overlap) + { + resD = context.AddIntrinsic(Intrinsic.Arm64InsVe | Intrinsic.Arm64VDWord, resD, Const(op.Vm & 1), resM, Const(0)); + } + else + { + resM = context.AddIntrinsic(Intrinsic.Arm64InsVe | Intrinsic.Arm64VDWord, m, Const(op.Vm & 1), resM, Const(0)); + } + } + + context.Copy(d, resD); + if (!overlap) + { + context.Copy(m, resM); + } + } + + private static void EmitVectorShuffleOpSimd32(ArmEmitterContext context, Func shuffleFunc) + { + OpCode32Simd op = (OpCode32Simd)context.CurrOp; + + Operand m = GetVecA32(op.Qm); + Operand d = GetVecA32(op.Qd); + Operand initialM = m; + Operand initialD = d; + + if (!op.Q) // Register swap: move relevant doubleword to side 0, for consistency. + { + m = EmitMoveDoubleWordToSide(context, m, op.Vm, 0); + d = EmitMoveDoubleWordToSide(context, d, op.Vd, 0); + } + + (Operand resM, Operand resD) = shuffleFunc(m, d); + + bool overlap = op.Qm == op.Qd; + + if (!op.Q) // Register insert. + { + resM = EmitDoubleWordInsert(context, initialM, EmitMoveDoubleWordToSide(context, resM, 0, op.Vm), op.Vm); + resD = EmitDoubleWordInsert(context, overlap ? resM : initialD, EmitMoveDoubleWordToSide(context, resD, 0, op.Vd), op.Vd); + } + + if (!overlap) + { + context.Copy(initialM, resM); + } + + context.Copy(initialD, resD); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdShift.cs b/src/ARMeilleure/Instructions/InstEmitSimdShift.cs new file mode 100644 index 00000000..19e41119 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdShift.cs @@ -0,0 +1,1827 @@ +// https://github.com/intel/ARM_NEON_2_x86_SSE/blob/master/NEON_2_SSE.h + +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using System.Reflection; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + using Func2I = Func; + + static partial class InstEmit + { +#region "Masks" + private static readonly long[] _masks_SliSri = new long[] // Replication masks. + { + 0x0101010101010101L, 0x0001000100010001L, 0x0000000100000001L, 0x0000000000000001L + }; +#endregion + + public static void Rshrn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64RshrnV, shift); + } + else if (Optimizations.UseSsse3) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + long roundConst = 1L << (shift - 1); + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Operand dLow = context.VectorZeroUpper64(d); + + Operand mask = default; + + switch (op.Size + 1) + { + case 1: mask = X86GetAllElements(context, (int)roundConst * 0x00010001); break; + case 2: mask = X86GetAllElements(context, (int)roundConst); break; + case 3: mask = X86GetAllElements(context, roundConst); break; + } + + Intrinsic addInst = X86PaddInstruction[op.Size + 1]; + + Operand res = context.AddIntrinsic(addInst, n, mask); + + Intrinsic srlInst = X86PsrlInstruction[op.Size + 1]; + + res = context.AddIntrinsic(srlInst, res, Const(shift)); + + Operand mask2 = X86GetAllElements(context, EvenMasks[op.Size]); + + res = context.AddIntrinsic(Intrinsic.X86Pshufb, res, mask2); + + Intrinsic movInst = op.RegisterSize == RegisterSize.Simd128 + ? Intrinsic.X86Movlhps + : Intrinsic.X86Movhlps; + + res = context.AddIntrinsic(movInst, dLow, res); + + context.Copy(d, res); + } + else + { + EmitVectorShrImmNarrowOpZx(context, round: true); + } + } + + public static void Shl_S(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarShiftBinaryOp(context, Intrinsic.Arm64ShlS, shift); + } + else + { + EmitScalarUnaryOpZx(context, (op1) => context.ShiftLeft(op1, Const(shift))); + } + } + + public static void Shl_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + int eSize = 8 << op.Size; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorShiftBinaryOp(context, Intrinsic.Arm64ShlV, shift); + } + else if (shift >= eSize) + { + if ((op.RegisterSize == RegisterSize.Simd64)) + { + Operand res = context.VectorZeroUpper64(GetVec(op.Rd)); + + context.Copy(GetVec(op.Rd), res); + } + } + else if (Optimizations.UseGfni && op.Size == 0) + { + Operand n = GetVec(op.Rn); + + ulong bitMatrix = X86GetGf2p8LogicalShiftLeft(shift); + + Operand vBitMatrix = X86GetElements(context, bitMatrix, bitMatrix); + + Operand res = context.AddIntrinsic(Intrinsic.X86Gf2p8affineqb, n, vBitMatrix, Const(0)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + Operand n = GetVec(op.Rn); + + Intrinsic sllInst = X86PsllInstruction[op.Size]; + + Operand res = context.AddIntrinsic(sllInst, n, Const(shift)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorUnaryOpZx(context, (op1) => context.ShiftLeft(op1, Const(shift))); + } + } + + public static void Shll_V(ArmEmitterContext context) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + int shift = 8 << op.Size; + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorUnaryOp(context, Intrinsic.Arm64ShllV); + } + else if (Optimizations.UseSse41) + { + Operand n = GetVec(op.Rn); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + } + + Intrinsic movsxInst = X86PmovsxInstruction[op.Size]; + + Operand res = context.AddIntrinsic(movsxInst, n); + + Intrinsic sllInst = X86PsllInstruction[op.Size + 1]; + + res = context.AddIntrinsic(sllInst, res, Const(shift)); + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorShImmWidenBinaryZx(context, (op1, op2) => context.ShiftLeft(op1, op2), shift); + } + } + + public static void Shrn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64ShrnV, shift); + } + else if (Optimizations.UseSsse3) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Operand dLow = context.VectorZeroUpper64(d); + + Intrinsic srlInst = X86PsrlInstruction[op.Size + 1]; + + Operand nShifted = context.AddIntrinsic(srlInst, n, Const(shift)); + + Operand mask = X86GetAllElements(context, EvenMasks[op.Size]); + + Operand res = context.AddIntrinsic(Intrinsic.X86Pshufb, nShifted, mask); + + Intrinsic movInst = op.RegisterSize == RegisterSize.Simd128 + ? Intrinsic.X86Movlhps + : Intrinsic.X86Movhlps; + + res = context.AddIntrinsic(movInst, dLow, res); + + context.Copy(d, res); + } + else + { + EmitVectorShrImmNarrowOpZx(context, round: false); + } + } + + public static void Sli_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + + InstEmitSimdHelperArm64.EmitScalarShiftTernaryOpRd(context, Intrinsic.Arm64SliS, shift); + } + else + { + EmitSli(context, scalar: true); + } + } + + public static void Sli_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64SliV, shift); + } + else + { + EmitSli(context, scalar: false); + } + } + + public static void Sqrshl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64SqrshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Signed | ShlRegFlags.Round | ShlRegFlags.Saturating); + } + } + + public static void Sqrshrn_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqrshrnS, shift); + } + else + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarSxSx); + } + } + + public static void Sqrshrn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqrshrnV, shift); + } + else + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxSx); + } + } + + public static void Sqrshrun_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqrshrunS, shift); + } + else + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarSxZx); + } + } + + public static void Sqrshrun_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqrshrunV, shift); + } + else + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxZx); + } + } + + public static void Sqshl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64SqshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Signed | ShlRegFlags.Saturating); + } + } + + public static void Sqshrn_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqshrnS, shift); + } + else + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarSxSx); + } + } + + public static void Sqshrn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqshrnV, shift); + } + else + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxSx); + } + } + + public static void Sqshrun_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqshrunS, shift); + } + else + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarSxZx); + } + } + + public static void Sqshrun_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64SqshrunV, shift); + } + else + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxZx); + } + } + + public static void Sri_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftTernaryOpRd(context, Intrinsic.Arm64SriS, shift); + } + else + { + EmitSri(context, scalar: true); + } + } + + public static void Sri_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64SriV, shift); + } + else + { + EmitSri(context, scalar: false); + } + } + + public static void Srshl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SrshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Signed | ShlRegFlags.Round); + } + } + + public static void Srshr_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftBinaryOp(context, Intrinsic.Arm64SrshrS, shift); + } + else + { + EmitScalarShrImmOpSx(context, ShrImmFlags.Round); + } + } + + public static void Srshr_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftBinaryOp(context, Intrinsic.Arm64SrshrV, shift); + } + else if (Optimizations.UseSse2 && op.Size > 0 && op.Size < 3) + { + int shift = GetImmShr(op); + int eSize = 8 << op.Size; + + Operand n = GetVec(op.Rn); + + Intrinsic sllInst = X86PsllInstruction[op.Size]; + + Operand res = context.AddIntrinsic(sllInst, n, Const(eSize - shift)); + + Intrinsic srlInst = X86PsrlInstruction[op.Size]; + + res = context.AddIntrinsic(srlInst, res, Const(eSize - 1)); + + Intrinsic sraInst = X86PsraInstruction[op.Size]; + + Operand nSra = context.AddIntrinsic(sraInst, n, Const(shift)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, nSra); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorShrImmOpSx(context, ShrImmFlags.Round); + } + } + + public static void Srsra_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftTernaryOpRd(context, Intrinsic.Arm64SrsraS, shift); + } + else + { + EmitScalarShrImmOpSx(context, ShrImmFlags.Round | ShrImmFlags.Accumulate); + } + } + + public static void Srsra_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64SrsraV, shift); + } + else if (Optimizations.UseSse2 && op.Size > 0 && op.Size < 3) + { + int shift = GetImmShr(op); + int eSize = 8 << op.Size; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Intrinsic sllInst = X86PsllInstruction[op.Size]; + + Operand res = context.AddIntrinsic(sllInst, n, Const(eSize - shift)); + + Intrinsic srlInst = X86PsrlInstruction[op.Size]; + + res = context.AddIntrinsic(srlInst, res, Const(eSize - 1)); + + Intrinsic sraInst = X86PsraInstruction[op.Size]; + + Operand nSra = context.AddIntrinsic(sraInst, n, Const(shift)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, nSra); + res = context.AddIntrinsic(addInst, res, d); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + EmitVectorShrImmOpSx(context, ShrImmFlags.Round | ShrImmFlags.Accumulate); + } + } + + public static void Sshl_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOp(context, Intrinsic.Arm64SshlS); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Scalar | ShlRegFlags.Signed); + } + } + + public static void Sshl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64SshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Signed); + } + } + + public static void Sshll_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorShiftBinaryOp(context, Intrinsic.Arm64SshllV, shift); + } + else if (Optimizations.UseSse41) + { + Operand n = GetVec(op.Rn); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + } + + Intrinsic movsxInst = X86PmovsxInstruction[op.Size]; + + Operand res = context.AddIntrinsic(movsxInst, n); + + if (shift != 0) + { + Intrinsic sllInst = X86PsllInstruction[op.Size + 1]; + + res = context.AddIntrinsic(sllInst, res, Const(shift)); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorShImmWidenBinarySx(context, (op1, op2) => context.ShiftLeft(op1, op2), shift); + } + } + + public static void Sshr_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftBinaryOp(context, Intrinsic.Arm64SshrS, shift); + } + else + { + EmitShrImmOp(context, ShrImmFlags.ScalarSx); + } + } + + public static void Sshr_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorShiftBinaryOp(context, Intrinsic.Arm64SshrV, shift); + } + else if (Optimizations.UseGfni && op.Size == 0) + { + Operand n = GetVec(op.Rn); + + ulong bitMatrix; + + if (shift < 8) + { + bitMatrix = X86GetGf2p8LogicalShiftLeft(-shift); + + // Extend sign-bit + bitMatrix |= 0x8080808080808080UL >> (64 - shift * 8); + } + else + { + // Replicate sign-bit into all bits + bitMatrix = 0x8080808080808080UL; + } + + Operand vBitMatrix = X86GetElements(context, bitMatrix, bitMatrix); + + Operand res = context.AddIntrinsic(Intrinsic.X86Gf2p8affineqb, n, vBitMatrix, Const(0)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else if (Optimizations.UseSse2 && op.Size > 0 && op.Size < 3) + { + Operand n = GetVec(op.Rn); + + Intrinsic sraInst = X86PsraInstruction[op.Size]; + + Operand res = context.AddIntrinsic(sraInst, n, Const(shift)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitShrImmOp(context, ShrImmFlags.VectorSx); + } + } + + public static void Ssra_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftTernaryOpRd(context, Intrinsic.Arm64SsraS, shift); + } + else + { + EmitScalarShrImmOpSx(context, ShrImmFlags.Accumulate); + } + } + + public static void Ssra_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64SsraV, shift); + } + else if (Optimizations.UseSse2 && op.Size > 0 && op.Size < 3) + { + int shift = GetImmShr(op); + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Intrinsic sraInst = X86PsraInstruction[op.Size]; + + Operand res = context.AddIntrinsic(sraInst, n, Const(shift)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, d); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + EmitVectorShrImmOpSx(context, ShrImmFlags.Accumulate); + } + } + + public static void Uqrshl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64UqrshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Round | ShlRegFlags.Saturating); + } + } + + public static void Uqrshrn_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64UqrshrnS, shift); + } + else + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarZxZx); + } + } + + public static void Uqrshrn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64UqrshrnV, shift); + } + else + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorZxZx); + } + } + + public static void Uqshl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorSaturatingBinaryOp(context, Intrinsic.Arm64UqshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Saturating); + } + } + + public static void Uqshrn_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64UqshrnS, shift); + } + else + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.ScalarZxZx); + } + } + + public static void Uqshrn_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorSaturatingShiftTernaryOpRd(context, Intrinsic.Arm64UqshrnV, shift); + } + else + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorZxZx); + } + } + + public static void Urshl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UrshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Round); + } + } + + public static void Urshr_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftBinaryOp(context, Intrinsic.Arm64UrshrS, shift); + } + else + { + EmitScalarShrImmOpZx(context, ShrImmFlags.Round); + } + } + + public static void Urshr_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftBinaryOp(context, Intrinsic.Arm64UrshrV, shift); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + int shift = GetImmShr(op); + int eSize = 8 << op.Size; + + Operand n = GetVec(op.Rn); + + Intrinsic sllInst = X86PsllInstruction[op.Size]; + + Operand res = context.AddIntrinsic(sllInst, n, Const(eSize - shift)); + + Intrinsic srlInst = X86PsrlInstruction[op.Size]; + + res = context.AddIntrinsic(srlInst, res, Const(eSize - 1)); + + Operand nSrl = context.AddIntrinsic(srlInst, n, Const(shift)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, nSrl); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorShrImmOpZx(context, ShrImmFlags.Round); + } + } + + public static void Ursra_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftTernaryOpRd(context, Intrinsic.Arm64UrsraS, shift); + } + else + { + EmitScalarShrImmOpZx(context, ShrImmFlags.Round | ShrImmFlags.Accumulate); + } + } + + public static void Ursra_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64UrsraV, shift); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + int shift = GetImmShr(op); + int eSize = 8 << op.Size; + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Intrinsic sllInst = X86PsllInstruction[op.Size]; + + Operand res = context.AddIntrinsic(sllInst, n, Const(eSize - shift)); + + Intrinsic srlInst = X86PsrlInstruction[op.Size]; + + res = context.AddIntrinsic(srlInst, res, Const(eSize - 1)); + + Operand nSrl = context.AddIntrinsic(srlInst, n, Const(shift)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, nSrl); + res = context.AddIntrinsic(addInst, res, d); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + EmitVectorShrImmOpZx(context, ShrImmFlags.Round | ShrImmFlags.Accumulate); + } + } + + public static void Ushl_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitScalarBinaryOp(context, Intrinsic.Arm64UshlS); + } + else + { + EmitShlRegOp(context, ShlRegFlags.Scalar); + } + } + + public static void Ushl_V(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64UshlV); + } + else + { + EmitShlRegOp(context, ShlRegFlags.None); + } + } + + public static void Ushll_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + + if (Optimizations.UseAdvSimd) + { + InstEmitSimdHelperArm64.EmitVectorShiftBinaryOp(context, Intrinsic.Arm64UshllV, shift); + } + else if (Optimizations.UseSse41) + { + Operand n = GetVec(op.Rn); + + if (op.RegisterSize == RegisterSize.Simd128) + { + n = context.AddIntrinsic(Intrinsic.X86Psrldq, n, Const(8)); + } + + Intrinsic movzxInst = X86PmovzxInstruction[op.Size]; + + Operand res = context.AddIntrinsic(movzxInst, n); + + if (shift != 0) + { + Intrinsic sllInst = X86PsllInstruction[op.Size + 1]; + + res = context.AddIntrinsic(sllInst, res, Const(shift)); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitVectorShImmWidenBinaryZx(context, (op1, op2) => context.ShiftLeft(op1, op2), shift); + } + } + + public static void Ushr_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftBinaryOp(context, Intrinsic.Arm64UshrS, shift); + } + else + { + EmitShrImmOp(context, ShrImmFlags.ScalarZx); + } + } + + public static void Ushr_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftBinaryOp(context, Intrinsic.Arm64UshrV, shift); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + int shift = GetImmShr(op); + + Operand n = GetVec(op.Rn); + + Intrinsic srlInst = X86PsrlInstruction[op.Size]; + + Operand res = context.AddIntrinsic(srlInst, n, Const(shift)); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(GetVec(op.Rd), res); + } + else + { + EmitShrImmOp(context, ShrImmFlags.VectorZx); + } + } + + public static void Usra_S(ArmEmitterContext context) + { + if (Optimizations.UseAdvSimd) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitScalarShiftTernaryOpRd(context, Intrinsic.Arm64UsraS, shift); + } + else + { + EmitScalarShrImmOpZx(context, ShrImmFlags.Accumulate); + } + } + + public static void Usra_V(ArmEmitterContext context) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + if (Optimizations.UseAdvSimd) + { + int shift = GetImmShr(op); + + InstEmitSimdHelperArm64.EmitVectorShiftTernaryOpRd(context, Intrinsic.Arm64UsraV, shift); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + int shift = GetImmShr(op); + + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Intrinsic srlInst = X86PsrlInstruction[op.Size]; + + Operand res = context.AddIntrinsic(srlInst, n, Const(shift)); + + Intrinsic addInst = X86PaddInstruction[op.Size]; + + res = context.AddIntrinsic(addInst, res, d); + + if (op.RegisterSize == RegisterSize.Simd64) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + EmitVectorShrImmOpZx(context, ShrImmFlags.Accumulate); + } + } + + [Flags] + private enum ShrImmFlags + { + Scalar = 1 << 0, + Signed = 1 << 1, + + Round = 1 << 2, + Accumulate = 1 << 3, + + ScalarSx = Scalar | Signed, + ScalarZx = Scalar, + + VectorSx = Signed, + VectorZx = 0 + } + + private static void EmitScalarShrImmOpSx(ArmEmitterContext context, ShrImmFlags flags) + { + EmitShrImmOp(context, ShrImmFlags.ScalarSx | flags); + } + + private static void EmitScalarShrImmOpZx(ArmEmitterContext context, ShrImmFlags flags) + { + EmitShrImmOp(context, ShrImmFlags.ScalarZx | flags); + } + + private static void EmitVectorShrImmOpSx(ArmEmitterContext context, ShrImmFlags flags) + { + EmitShrImmOp(context, ShrImmFlags.VectorSx | flags); + } + + private static void EmitVectorShrImmOpZx(ArmEmitterContext context, ShrImmFlags flags) + { + EmitShrImmOp(context, ShrImmFlags.VectorZx | flags); + } + + private static void EmitShrImmOp(ArmEmitterContext context, ShrImmFlags flags) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + Operand res = context.VectorZero(); + + bool scalar = (flags & ShrImmFlags.Scalar) != 0; + bool signed = (flags & ShrImmFlags.Signed) != 0; + bool round = (flags & ShrImmFlags.Round) != 0; + bool accumulate = (flags & ShrImmFlags.Accumulate) != 0; + + int shift = GetImmShr(op); + + long roundConst = 1L << (shift - 1); + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + for (int index = 0; index < elems; index++) + { + Operand e = EmitVectorExtract(context, op.Rn, index, op.Size, signed); + + if (op.Size <= 2) + { + if (round) + { + e = context.Add(e, Const(roundConst)); + } + + e = signed ? context.ShiftRightSI(e, Const(shift)) : context.ShiftRightUI(e, Const(shift)); + } + else /* if (op.Size == 3) */ + { + e = EmitShrImm64(context, e, signed, round ? roundConst : 0L, shift); + } + + if (accumulate) + { + Operand de = EmitVectorExtract(context, op.Rd, index, op.Size, signed); + + e = context.Add(e, de); + } + + res = EmitVectorInsert(context, res, e, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitVectorShrImmNarrowOpZx(ArmEmitterContext context, bool round) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + + long roundConst = 1L << (shift - 1); + + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); + + for (int index = 0; index < elems; index++) + { + Operand e = EmitVectorExtractZx(context, op.Rn, index, op.Size + 1); + + if (round) + { + e = context.Add(e, Const(roundConst)); + } + + e = context.ShiftRightUI(e, Const(shift)); + + res = EmitVectorInsert(context, res, e, part + index, op.Size); + } + + context.Copy(d, res); + } + + [Flags] + private enum ShrImmSaturatingNarrowFlags + { + Scalar = 1 << 0, + SignedSrc = 1 << 1, + SignedDst = 1 << 2, + + Round = 1 << 3, + + ScalarSxSx = Scalar | SignedSrc | SignedDst, + ScalarSxZx = Scalar | SignedSrc, + ScalarZxZx = Scalar, + + VectorSxSx = SignedSrc | SignedDst, + VectorSxZx = SignedSrc, + VectorZxZx = 0 + } + + private static void EmitRoundShrImmSaturatingNarrowOp(ArmEmitterContext context, ShrImmSaturatingNarrowFlags flags) + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.Round | flags); + } + + private static void EmitShrImmSaturatingNarrowOp(ArmEmitterContext context, ShrImmSaturatingNarrowFlags flags) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + bool scalar = (flags & ShrImmSaturatingNarrowFlags.Scalar) != 0; + bool signedSrc = (flags & ShrImmSaturatingNarrowFlags.SignedSrc) != 0; + bool signedDst = (flags & ShrImmSaturatingNarrowFlags.SignedDst) != 0; + bool round = (flags & ShrImmSaturatingNarrowFlags.Round) != 0; + + int shift = GetImmShr(op); + + long roundConst = 1L << (shift - 1); + + int elems = !scalar ? 8 >> op.Size : 1; + + int part = !scalar && (op.RegisterSize == RegisterSize.Simd128) ? elems : 0; + + Operand d = GetVec(op.Rd); + + Operand res = part == 0 ? context.VectorZero() : context.Copy(d); + + for (int index = 0; index < elems; index++) + { + Operand e = EmitVectorExtract(context, op.Rn, index, op.Size + 1, signedSrc); + + if (op.Size <= 1 || !round) + { + if (round) + { + e = context.Add(e, Const(roundConst)); + } + + e = signedSrc ? context.ShiftRightSI(e, Const(shift)) : context.ShiftRightUI(e, Const(shift)); + } + else /* if (op.Size == 2 && round) */ + { + e = EmitShrImm64(context, e, signedSrc, roundConst, shift); // shift <= 32 + } + + e = signedSrc ? EmitSignedSrcSatQ(context, e, op.Size, signedDst) : EmitUnsignedSrcSatQ(context, e, op.Size, signedDst); + + res = EmitVectorInsert(context, res, e, part + index, op.Size); + } + + context.Copy(d, res); + } + + // dst64 = (Int(src64, signed) + roundConst) >> shift; + private static Operand EmitShrImm64( + ArmEmitterContext context, + Operand value, + bool signed, + long roundConst, + int shift) + { + MethodInfo info = signed + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SignedShrImm64)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.UnsignedShrImm64)); + + return context.Call(info, value, Const(roundConst), Const(shift)); + } + + private static void EmitVectorShImmWidenBinarySx(ArmEmitterContext context, Func2I emit, int imm) + { + EmitVectorShImmWidenBinaryOp(context, emit, imm, signed: true); + } + + private static void EmitVectorShImmWidenBinaryZx(ArmEmitterContext context, Func2I emit, int imm) + { + EmitVectorShImmWidenBinaryOp(context, emit, imm, signed: false); + } + + private static void EmitVectorShImmWidenBinaryOp(ArmEmitterContext context, Func2I emit, int imm, bool signed) + { + OpCodeSimd op = (OpCodeSimd)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = 8 >> op.Size; + + int part = op.RegisterSize == RegisterSize.Simd128 ? elems : 0; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, part + index, op.Size, signed); + + res = EmitVectorInsert(context, res, emit(ne, Const(imm)), index, op.Size + 1); + } + + context.Copy(GetVec(op.Rd), res); + } + + private static void EmitSli(ArmEmitterContext context, bool scalar) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShl(op); + int eSize = 8 << op.Size; + + ulong mask = shift != 0 ? ulong.MaxValue >> (64 - shift) : 0UL; + + if (shift >= eSize) + { + if ((op.RegisterSize == RegisterSize.Simd64) || scalar) + { + Operand res = context.VectorZeroUpper64(GetVec(op.Rd)); + + context.Copy(GetVec(op.Rd), res); + } + } + else if (Optimizations.UseGfni && op.Size == 0) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + ulong bitMatrix = X86GetGf2p8LogicalShiftLeft(shift); + + Operand vBitMatrix = X86GetElements(context, bitMatrix, bitMatrix); + + Operand nShifted = context.AddIntrinsic(Intrinsic.X86Gf2p8affineqb, n, vBitMatrix, Const(0)); + + Operand dMask = X86GetAllElements(context, (long)mask * _masks_SliSri[op.Size]); + + Operand dMasked = context.AddIntrinsic(Intrinsic.X86Pand, d, dMask); + + Operand res = context.AddIntrinsic(Intrinsic.X86Por, nShifted, dMasked); + + if ((op.RegisterSize == RegisterSize.Simd64) || scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Intrinsic sllInst = X86PsllInstruction[op.Size]; + + Operand nShifted = context.AddIntrinsic(sllInst, n, Const(shift)); + + Operand dMask = X86GetAllElements(context, (long)mask * _masks_SliSri[op.Size]); + + Operand dMasked = context.AddIntrinsic(Intrinsic.X86Pand, d, dMask); + + Operand res = context.AddIntrinsic(Intrinsic.X86Por, nShifted, dMasked); + + if ((op.RegisterSize == RegisterSize.Simd64) || scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + Operand res = context.VectorZero(); + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + + Operand neShifted = context.ShiftLeft(ne, Const(shift)); + + Operand de = EmitVectorExtractZx(context, op.Rd, index, op.Size); + + Operand deMasked = context.BitwiseAnd(de, Const(mask)); + + Operand e = context.BitwiseOr(neShifted, deMasked); + + res = EmitVectorInsert(context, res, e, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + private static void EmitSri(ArmEmitterContext context, bool scalar) + { + OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + int eSize = 8 << op.Size; + + ulong mask = (ulong.MaxValue << (eSize - shift)) & (ulong.MaxValue >> (64 - eSize)); + + if (shift >= eSize) + { + if ((op.RegisterSize == RegisterSize.Simd64) || scalar) + { + Operand res = context.VectorZeroUpper64(GetVec(op.Rd)); + + context.Copy(GetVec(op.Rd), res); + } + } + else if (Optimizations.UseGfni && op.Size == 0) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + ulong bitMatrix = X86GetGf2p8LogicalShiftLeft(-shift); + + Operand vBitMatrix = X86GetElements(context, bitMatrix, bitMatrix); + + Operand nShifted = context.AddIntrinsic(Intrinsic.X86Gf2p8affineqb, n, vBitMatrix, Const(0)); + + Operand dMask = X86GetAllElements(context, (long)mask * _masks_SliSri[op.Size]); + + Operand dMasked = context.AddIntrinsic(Intrinsic.X86Pand, d, dMask); + + Operand res = context.AddIntrinsic(Intrinsic.X86Por, nShifted, dMasked); + + if ((op.RegisterSize == RegisterSize.Simd64) || scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else if (Optimizations.UseSse2 && op.Size > 0) + { + Operand d = GetVec(op.Rd); + Operand n = GetVec(op.Rn); + + Intrinsic srlInst = X86PsrlInstruction[op.Size]; + + Operand nShifted = context.AddIntrinsic(srlInst, n, Const(shift)); + + Operand dMask = X86GetAllElements(context, (long)mask * _masks_SliSri[op.Size]); + + Operand dMasked = context.AddIntrinsic(Intrinsic.X86Pand, d, dMask); + + Operand res = context.AddIntrinsic(Intrinsic.X86Por, nShifted, dMasked); + + if ((op.RegisterSize == RegisterSize.Simd64) || scalar) + { + res = context.VectorZeroUpper64(res); + } + + context.Copy(d, res); + } + else + { + Operand res = context.VectorZero(); + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtractZx(context, op.Rn, index, op.Size); + + Operand neShifted = shift != 64 ? context.ShiftRightUI(ne, Const(shift)) : Const(0UL); + + Operand de = EmitVectorExtractZx(context, op.Rd, index, op.Size); + + Operand deMasked = context.BitwiseAnd(de, Const(mask)); + + Operand e = context.BitwiseOr(neShifted, deMasked); + + res = EmitVectorInsert(context, res, e, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + } + + [Flags] + private enum ShlRegFlags + { + None = 0, + Scalar = 1 << 0, + Signed = 1 << 1, + Round = 1 << 2, + Saturating = 1 << 3 + } + + private static void EmitShlRegOp(ArmEmitterContext context, ShlRegFlags flags = ShlRegFlags.None) + { + bool scalar = flags.HasFlag(ShlRegFlags.Scalar); + bool signed = flags.HasFlag(ShlRegFlags.Signed); + bool round = flags.HasFlag(ShlRegFlags.Round); + bool saturating = flags.HasFlag(ShlRegFlags.Saturating); + + OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = !scalar ? op.GetBytesCount() >> op.Size : 1; + + for (int index = 0; index < elems; index++) + { + Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size, signed); + Operand me = EmitVectorExtractSx(context, op.Rm, index << op.Size, size: 0); + + Operand e = !saturating + ? EmitShlReg(context, ne, context.ConvertI64ToI32(me), round, op.Size, signed) + : EmitShlRegSatQ(context, ne, context.ConvertI64ToI32(me), round, op.Size, signed); + + res = EmitVectorInsert(context, res, e, index, op.Size); + } + + context.Copy(GetVec(op.Rd), res); + } + + // long SignedShlReg(long op, int shiftLsB, bool round, int size); + // ulong UnsignedShlReg(ulong op, int shiftLsB, bool round, int size); + private static Operand EmitShlReg(ArmEmitterContext context, Operand op, Operand shiftLsB, bool round, int size, bool signed) + { + int eSize = 8 << size; + + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(shiftLsB.Type == OperandType.I32); + Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64); + + Operand lbl1 = Label(); + Operand lblEnd = Label(); + + Operand eSizeOp = Const(eSize); + Operand zero = Const(0); + Operand zeroL = Const(0L); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), op); + + context.BranchIf(lbl1, shiftLsB, zero, Comparison.GreaterOrEqual); + context.Copy(res, signed + ? EmitSignedShrReg(context, op, context.Negate(shiftLsB), round, eSize) + : EmitUnsignedShrReg(context, op, context.Negate(shiftLsB), round, eSize)); + context.Branch(lblEnd); + + context.MarkLabel(lbl1); + context.BranchIf(lblEnd, shiftLsB, zero, Comparison.LessOrEqual); + Operand shl = context.ShiftLeft(op, shiftLsB); + Operand isGreaterOrEqual = context.ICompareGreaterOrEqual(shiftLsB, eSizeOp); + context.Copy(res, context.ConditionalSelect(isGreaterOrEqual, zeroL, shl)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // long SignedShlRegSatQ(long op, int shiftLsB, bool round, int size); + // ulong UnsignedShlRegSatQ(ulong op, int shiftLsB, bool round, int size); + private static Operand EmitShlRegSatQ(ArmEmitterContext context, Operand op, Operand shiftLsB, bool round, int size, bool signed) + { + int eSize = 8 << size; + + Debug.Assert(op.Type == OperandType.I64); + Debug.Assert(shiftLsB.Type == OperandType.I32); + Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64); + + Operand lbl1 = Label(); + Operand lbl2 = Label(); + Operand lblEnd = Label(); + + Operand eSizeOp = Const(eSize); + Operand zero = Const(0); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), op); + + context.BranchIf(lbl1, shiftLsB, zero, Comparison.GreaterOrEqual); + context.Copy(res, signed + ? EmitSignedShrReg(context, op, context.Negate(shiftLsB), round, eSize) + : EmitUnsignedShrReg(context, op, context.Negate(shiftLsB), round, eSize)); + context.Branch(lblEnd); + + context.MarkLabel(lbl1); + context.BranchIf(lblEnd, shiftLsB, zero, Comparison.LessOrEqual); + context.BranchIf(lbl2, shiftLsB, eSizeOp, Comparison.Less); + context.Copy(res, signed + ? EmitSignedSignSatQ(context, op, size) + : EmitUnsignedSignSatQ(context, op, size)); + context.Branch(lblEnd); + + context.MarkLabel(lbl2); + Operand shl = context.ShiftLeft(op, shiftLsB); + if (eSize == 64) + { + Operand sarOrShr = signed + ? context.ShiftRightSI(shl, shiftLsB) + : context.ShiftRightUI(shl, shiftLsB); + context.Copy(res, shl); + context.BranchIf(lblEnd, sarOrShr, op, Comparison.Equal); + context.Copy(res, signed + ? EmitSignedSignSatQ(context, op, size) + : EmitUnsignedSignSatQ(context, op, size)); + } + else + { + context.Copy(res, signed + ? EmitSignedSrcSatQ(context, shl, size, signedDst: true) + : EmitUnsignedSrcSatQ(context, shl, size, signedDst: false)); + } + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + + // shift := [1, 128]; eSize := {8, 16, 32, 64}. + // long SignedShrReg(long op, int shift, bool round, int eSize); + private static Operand EmitSignedShrReg(ArmEmitterContext context, Operand op, Operand shift, bool round, int eSize) + { + if (round) + { + Operand lblEnd = Label(); + + Operand eSizeOp = Const(eSize); + Operand zeroL = Const(0L); + Operand one = Const(1); + Operand oneL = Const(1L); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), zeroL); + + context.BranchIf(lblEnd, shift, eSizeOp, Comparison.GreaterOrEqual); + Operand roundConst = context.ShiftLeft(oneL, context.Subtract(shift, one)); + Operand add = context.Add(op, roundConst); + Operand sar = context.ShiftRightSI(add, shift); + if (eSize == 64) + { + Operand shr = context.ShiftRightUI(add, shift); + Operand left = context.BitwiseAnd(context.Negate(op), context.BitwiseExclusiveOr(op, add)); + Operand isLess = context.ICompareLess(left, zeroL); + context.Copy(res, context.ConditionalSelect(isLess, shr, sar)); + } + else + { + context.Copy(res, sar); + } + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + else + { + Operand lblEnd = Label(); + + Operand eSizeOp = Const(eSize); + Operand zeroL = Const(0L); + Operand negOneL = Const(-1L); + + Operand sar = context.ShiftRightSI(op, shift); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), sar); + + context.BranchIf(lblEnd, shift, eSizeOp, Comparison.Less); + Operand isLess = context.ICompareLess(op, zeroL); + context.Copy(res, context.ConditionalSelect(isLess, negOneL, zeroL)); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + } + + // shift := [1, 128]; eSize := {8, 16, 32, 64}. + // ulong UnsignedShrReg(ulong op, int shift, bool round, int eSize); + private static Operand EmitUnsignedShrReg(ArmEmitterContext context, Operand op, Operand shift, bool round, int eSize) + { + if (round) + { + Operand lblEnd = Label(); + + Operand zeroUL = Const(0UL); + Operand one = Const(1); + Operand oneUL = Const(1UL); + Operand eSizeMaxOp = Const(64); + Operand oneShl63UL = Const(1UL << 63); + + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), zeroUL); + + context.BranchIf(lblEnd, shift, eSizeMaxOp, Comparison.Greater); + Operand roundConst = context.ShiftLeft(oneUL, context.Subtract(shift, one)); + Operand add = context.Add(op, roundConst); + Operand shr = context.ShiftRightUI(add, shift); + Operand isEqual = context.ICompareEqual(shift, eSizeMaxOp); + context.Copy(res, context.ConditionalSelect(isEqual, zeroUL, shr)); + if (eSize == 64) + { + context.BranchIf(lblEnd, add, op, Comparison.GreaterOrEqualUI); + Operand right = context.BitwiseOr(shr, context.ShiftRightUI(oneShl63UL, context.Subtract(shift, one))); + context.Copy(res, context.ConditionalSelect(isEqual, oneUL, right)); + } + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + else + { + Operand lblEnd = Label(); + + Operand eSizeOp = Const(eSize); + Operand zeroUL = Const(0UL); + + Operand shr = context.ShiftRightUI(op, shift); + Operand res = context.Copy(context.AllocateLocal(OperandType.I64), shr); + + context.BranchIf(lblEnd, shift, eSizeOp, Comparison.Less); + context.Copy(res, zeroUL); + context.Branch(lblEnd); + + context.MarkLabel(lblEnd); + + return res; + } + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSimdShift32.cs b/src/ARMeilleure/Instructions/InstEmitSimdShift32.cs new file mode 100644 index 00000000..9ac68088 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSimdShift32.cs @@ -0,0 +1,389 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using System.Diagnostics; +using System.Reflection; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper; +using static ARMeilleure.Instructions.InstEmitSimdHelper32; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void Vqrshrn(ArmEmitterContext context) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + + EmitRoundShrImmSaturatingNarrowOp(context, op.U ? ShrImmSaturatingNarrowFlags.VectorZxZx : ShrImmSaturatingNarrowFlags.VectorSxSx); + } + + public static void Vqrshrun(ArmEmitterContext context) + { + EmitRoundShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxZx); + } + + public static void Vqshrn(ArmEmitterContext context) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + + EmitShrImmSaturatingNarrowOp(context, op.U ? ShrImmSaturatingNarrowFlags.VectorZxZx : ShrImmSaturatingNarrowFlags.VectorSxSx); + } + + public static void Vqshrun(ArmEmitterContext context) + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.VectorSxZx); + } + + public static void Vrshr(ArmEmitterContext context) + { + EmitRoundShrImmOp(context, accumulate: false); + } + + public static void Vrshrn(ArmEmitterContext context) + { + EmitRoundShrImmNarrowOp(context, signed: false); + } + + public static void Vrsra(ArmEmitterContext context) + { + EmitRoundShrImmOp(context, accumulate: true); + } + + public static void Vshl(ArmEmitterContext context) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + + EmitVectorUnaryOpZx32(context, (op1) => context.ShiftLeft(op1, Const(op.Shift))); + } + + public static void Vshl_I(ArmEmitterContext context) + { + OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp; + + if (op.U) + { + EmitVectorBinaryOpZx32(context, (op1, op2) => EmitShlRegOp(context, op2, op1, op.Size, true)); + } + else + { + EmitVectorBinaryOpSx32(context, (op1, op2) => EmitShlRegOp(context, op2, op1, op.Size, false)); + } + } + + public static void Vshll(ArmEmitterContext context) + { + OpCode32SimdShImmLong op = (OpCode32SimdShImmLong)context.CurrOp; + + Operand res = context.VectorZero(); + + int elems = op.GetBytesCount() >> op.Size; + + for (int index = 0; index < elems; index++) + { + Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, !op.U); + + if (op.Size == 2) + { + if (op.U) + { + me = context.ZeroExtend32(OperandType.I64, me); + } + else + { + me = context.SignExtend32(OperandType.I64, me); + } + } + + me = context.ShiftLeft(me, Const(op.Shift)); + + res = EmitVectorInsert(context, res, me, index, op.Size + 1); + } + + context.Copy(GetVecA32(op.Qd), res); + } + + public static void Vshr(ArmEmitterContext context) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + int shift = GetImmShr(op); + int maxShift = (8 << op.Size) - 1; + + if (op.U) + { + EmitVectorUnaryOpZx32(context, (op1) => (shift > maxShift) ? Const(op1.Type, 0) : context.ShiftRightUI(op1, Const(shift))); + } + else + { + EmitVectorUnaryOpSx32(context, (op1) => context.ShiftRightSI(op1, Const(Math.Min(maxShift, shift)))); + } + } + + public static void Vshrn(ArmEmitterContext context) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + int shift = GetImmShr(op); + + EmitVectorUnaryNarrowOp32(context, (op1) => context.ShiftRightUI(op1, Const(shift))); + } + + public static void Vsra(ArmEmitterContext context) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + int shift = GetImmShr(op); + int maxShift = (8 << op.Size) - 1; + + if (op.U) + { + EmitVectorImmBinaryQdQmOpZx32(context, (op1, op2) => + { + Operand shiftRes = shift > maxShift ? Const(op2.Type, 0) : context.ShiftRightUI(op2, Const(shift)); + + return context.Add(op1, shiftRes); + }); + } + else + { + EmitVectorImmBinaryQdQmOpSx32(context, (op1, op2) => context.Add(op1, context.ShiftRightSI(op2, Const(Math.Min(maxShift, shift))))); + } + } + + public static void EmitRoundShrImmOp(ArmEmitterContext context, bool accumulate) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + int shift = GetImmShr(op); + long roundConst = 1L << (shift - 1); + + if (op.U) + { + if (op.Size < 2) + { + EmitVectorUnaryOpZx32(context, (op1) => + { + op1 = context.Add(op1, Const(op1.Type, roundConst)); + + return context.ShiftRightUI(op1, Const(shift)); + }, accumulate); + } + else if (op.Size == 2) + { + EmitVectorUnaryOpZx32(context, (op1) => + { + op1 = context.ZeroExtend32(OperandType.I64, op1); + op1 = context.Add(op1, Const(op1.Type, roundConst)); + + return context.ConvertI64ToI32(context.ShiftRightUI(op1, Const(shift))); + }, accumulate); + } + else /* if (op.Size == 3) */ + { + EmitVectorUnaryOpZx32(context, (op1) => EmitShrImm64(context, op1, signed: false, roundConst, shift), accumulate); + } + } + else + { + if (op.Size < 2) + { + EmitVectorUnaryOpSx32(context, (op1) => + { + op1 = context.Add(op1, Const(op1.Type, roundConst)); + + return context.ShiftRightSI(op1, Const(shift)); + }, accumulate); + } + else if (op.Size == 2) + { + EmitVectorUnaryOpSx32(context, (op1) => + { + op1 = context.SignExtend32(OperandType.I64, op1); + op1 = context.Add(op1, Const(op1.Type, roundConst)); + + return context.ConvertI64ToI32(context.ShiftRightSI(op1, Const(shift))); + }, accumulate); + } + else /* if (op.Size == 3) */ + { + EmitVectorUnaryOpZx32(context, (op1) => EmitShrImm64(context, op1, signed: true, roundConst, shift), accumulate); + } + } + } + + private static void EmitRoundShrImmNarrowOp(ArmEmitterContext context, bool signed) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + + int shift = GetImmShr(op); + long roundConst = 1L << (shift - 1); + + EmitVectorUnaryNarrowOp32(context, (op1) => + { + if (op.Size <= 1) + { + op1 = context.Add(op1, Const(op1.Type, roundConst)); + op1 = signed ? context.ShiftRightSI(op1, Const(shift)) : context.ShiftRightUI(op1, Const(shift)); + } + else /* if (op.Size == 2 && round) */ + { + op1 = EmitShrImm64(context, op1, signed, roundConst, shift); // shift <= 32 + } + + return op1; + }, signed); + } + + private static Operand EmitShlRegOp(ArmEmitterContext context, Operand op, Operand shiftLsB, int size, bool unsigned) + { + if (shiftLsB.Type == OperandType.I64) + { + shiftLsB = context.ConvertI64ToI32(shiftLsB); + } + + shiftLsB = context.SignExtend8(OperandType.I32, shiftLsB); + Debug.Assert((uint)size < 4u); + + Operand negShiftLsB = context.Negate(shiftLsB); + + Operand isPositive = context.ICompareGreaterOrEqual(shiftLsB, Const(0)); + + Operand shl = context.ShiftLeft(op, shiftLsB); + Operand shr = unsigned ? context.ShiftRightUI(op, negShiftLsB) : context.ShiftRightSI(op, negShiftLsB); + + Operand res = context.ConditionalSelect(isPositive, shl, shr); + + if (unsigned) + { + Operand isOutOfRange = context.BitwiseOr( + context.ICompareGreaterOrEqual(shiftLsB, Const(8 << size)), + context.ICompareGreaterOrEqual(negShiftLsB, Const(8 << size))); + + return context.ConditionalSelect(isOutOfRange, Const(op.Type, 0), res); + } + else + { + Operand isOutOfRange0 = context.ICompareGreaterOrEqual(shiftLsB, Const(8 << size)); + Operand isOutOfRangeN = context.ICompareGreaterOrEqual(negShiftLsB, Const(8 << size)); + + // Also zero if shift is too negative, but value was positive. + isOutOfRange0 = context.BitwiseOr(isOutOfRange0, context.BitwiseAnd(isOutOfRangeN, context.ICompareGreaterOrEqual(op, Const(op.Type, 0)))); + + Operand min = (op.Type == OperandType.I64) ? Const(-1L) : Const(-1); + + return context.ConditionalSelect(isOutOfRange0, Const(op.Type, 0), context.ConditionalSelect(isOutOfRangeN, min, res)); + } + } + + [Flags] + private enum ShrImmSaturatingNarrowFlags + { + Scalar = 1 << 0, + SignedSrc = 1 << 1, + SignedDst = 1 << 2, + + Round = 1 << 3, + + ScalarSxSx = Scalar | SignedSrc | SignedDst, + ScalarSxZx = Scalar | SignedSrc, + ScalarZxZx = Scalar, + + VectorSxSx = SignedSrc | SignedDst, + VectorSxZx = SignedSrc, + VectorZxZx = 0 + } + + private static void EmitRoundShrImmSaturatingNarrowOp(ArmEmitterContext context, ShrImmSaturatingNarrowFlags flags) + { + EmitShrImmSaturatingNarrowOp(context, ShrImmSaturatingNarrowFlags.Round | flags); + } + + private static void EmitShrImmSaturatingNarrowOp(ArmEmitterContext context, ShrImmSaturatingNarrowFlags flags) + { + OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp; + + bool scalar = (flags & ShrImmSaturatingNarrowFlags.Scalar) != 0; + bool signedSrc = (flags & ShrImmSaturatingNarrowFlags.SignedSrc) != 0; + bool signedDst = (flags & ShrImmSaturatingNarrowFlags.SignedDst) != 0; + bool round = (flags & ShrImmSaturatingNarrowFlags.Round) != 0; + + if (scalar) + { + // TODO: Support scalar operation. + throw new NotImplementedException(); + } + + int shift = GetImmShr(op); + long roundConst = 1L << (shift - 1); + + EmitVectorUnaryNarrowOp32(context, (op1) => + { + if (op.Size <= 1 || !round) + { + if (round) + { + op1 = context.Add(op1, Const(op1.Type, roundConst)); + } + + op1 = signedSrc ? context.ShiftRightSI(op1, Const(shift)) : context.ShiftRightUI(op1, Const(shift)); + } + else /* if (op.Size == 2 && round) */ + { + op1 = EmitShrImm64(context, op1, signedSrc, roundConst, shift); // shift <= 32 + } + + return EmitSatQ(context, op1, 8 << op.Size, signedSrc, signedDst); + }, signedSrc); + } + + private static int GetImmShr(OpCode32SimdShImm op) + { + return (8 << op.Size) - op.Shift; // Shr amount is flipped. + } + + // dst64 = (Int(src64, signed) + roundConst) >> shift; + private static Operand EmitShrImm64( + ArmEmitterContext context, + Operand value, + bool signed, + long roundConst, + int shift) + { + MethodInfo info = signed + ? typeof(SoftFallback).GetMethod(nameof(SoftFallback.SignedShrImm64)) + : typeof(SoftFallback).GetMethod(nameof(SoftFallback.UnsignedShrImm64)); + + return context.Call(info, value, Const(roundConst), Const(shift)); + } + + private static Operand EmitSatQ(ArmEmitterContext context, Operand value, int eSize, bool signedSrc, bool signedDst) + { + Debug.Assert(eSize <= 32); + + long intMin = signedDst ? -(1L << (eSize - 1)) : 0; + long intMax = signedDst ? (1L << (eSize - 1)) - 1 : (1L << eSize) - 1; + + Operand gt = signedSrc + ? context.ICompareGreater(value, Const(value.Type, intMax)) + : context.ICompareGreaterUI(value, Const(value.Type, intMax)); + + Operand lt = signedSrc + ? context.ICompareLess(value, Const(value.Type, intMin)) + : context.ICompareLessUI(value, Const(value.Type, intMin)); + + value = context.ConditionalSelect(gt, Const(value.Type, intMax), value); + value = context.ConditionalSelect(lt, Const(value.Type, intMin), value); + + Operand lblNoSat = Label(); + + context.BranchIfFalse(lblNoSat, context.BitwiseOr(gt, lt)); + + SetFpFlag(context, FPState.QcFlag, Const(1)); + + context.MarkLabel(lblNoSat); + + return value; + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSystem.cs b/src/ARMeilleure/Instructions/InstEmitSystem.cs new file mode 100644 index 00000000..f84829aa --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSystem.cs @@ -0,0 +1,248 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using System.Reflection; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit + { + private const int DczSizeLog2 = 4; // Log2 size in words + public const int DczSizeInBytes = 4 << DczSizeLog2; + + public static void Isb(ArmEmitterContext context) + { + // Execute as no-op. + } + + public static void Mrs(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + MethodInfo info; + + switch (GetPackedId(op)) + { + case 0b11_011_0000_0000_001: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCtrEl0)); break; + case 0b11_011_0000_0000_111: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetDczidEl0)); break; + case 0b11_011_0100_0010_000: EmitGetNzcv(context); return; + case 0b11_011_0100_0100_000: EmitGetFpcr(context); return; + case 0b11_011_0100_0100_001: EmitGetFpsr(context); return; + case 0b11_011_1101_0000_010: EmitGetTpidrEl0(context); return; + case 0b11_011_1101_0000_011: EmitGetTpidrroEl0(context); return; + case 0b11_011_1110_0000_000: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntfrqEl0)); break; + case 0b11_011_1110_0000_001: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntpctEl0)); break; + case 0b11_011_1110_0000_010: info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntvctEl0)); break; + + default: throw new NotImplementedException($"Unknown MRS 0x{op.RawOpCode:X8} at 0x{op.Address:X16}."); + } + + SetIntOrZR(context, op.Rt, context.Call(info)); + } + + public static void Msr(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + switch (GetPackedId(op)) + { + case 0b11_011_0100_0010_000: EmitSetNzcv(context); return; + case 0b11_011_0100_0100_000: EmitSetFpcr(context); return; + case 0b11_011_0100_0100_001: EmitSetFpsr(context); return; + case 0b11_011_1101_0000_010: EmitSetTpidrEl0(context); return; + + default: throw new NotImplementedException($"Unknown MSR 0x{op.RawOpCode:X8} at 0x{op.Address:X16}."); + } + } + + public static void Nop(ArmEmitterContext context) + { + // Do nothing. + } + + public static void Sys(ArmEmitterContext context) + { + // This instruction is used to do some operations on the CPU like cache invalidation, + // address translation and the like. + // We treat it as no-op here since we don't have any cache being emulated anyway. + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + switch (GetPackedId(op)) + { + case 0b11_011_0111_0100_001: + { + // DC ZVA + Operand t = GetIntOrZR(context, op.Rt); + + for (long offset = 0; offset < DczSizeInBytes; offset += 8) + { + Operand address = context.Add(t, Const(offset)); + + InstEmitMemoryHelper.EmitStore(context, address, RegisterConsts.ZeroIndex, 3); + } + + break; + } + + // No-op + case 0b11_011_0111_1110_001: // DC CIVAC + break; + + case 0b11_011_0111_0101_001: // IC IVAU + Operand target = Register(op.Rt, RegisterType.Integer, OperandType.I64); + context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.InvalidateCacheLine)), target); + break; + } + } + + private static int GetPackedId(OpCodeSystem op) + { + int id; + + id = op.Op2 << 0; + id |= op.CRm << 3; + id |= op.CRn << 7; + id |= op.Op1 << 11; + id |= op.Op0 << 14; + + return id; + } + + private static void EmitGetNzcv(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + Operand nzcv = context.ShiftLeft(GetFlag(PState.VFlag), Const((int)PState.VFlag)); + nzcv = context.BitwiseOr(nzcv, context.ShiftLeft(GetFlag(PState.CFlag), Const((int)PState.CFlag))); + nzcv = context.BitwiseOr(nzcv, context.ShiftLeft(GetFlag(PState.ZFlag), Const((int)PState.ZFlag))); + nzcv = context.BitwiseOr(nzcv, context.ShiftLeft(GetFlag(PState.NFlag), Const((int)PState.NFlag))); + + SetIntOrZR(context, op.Rt, nzcv); + } + + private static void EmitGetFpcr(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + Operand fpcr = Const(0); + + for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++) + { + if (FPCR.Mask.HasFlag((FPCR)(1u << flag))) + { + fpcr = context.BitwiseOr(fpcr, context.ShiftLeft(GetFpFlag((FPState)flag), Const(flag))); + } + } + + SetIntOrZR(context, op.Rt, fpcr); + } + + private static void EmitGetFpsr(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + context.SyncQcFlag(); + + Operand fpsr = Const(0); + + for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++) + { + if (FPSR.Mask.HasFlag((FPSR)(1u << flag))) + { + fpsr = context.BitwiseOr(fpsr, context.ShiftLeft(GetFpFlag((FPState)flag), Const(flag))); + } + } + + SetIntOrZR(context, op.Rt, fpsr); + } + + private static void EmitGetTpidrEl0(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + + Operand result = context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset()))); + + SetIntOrZR(context, op.Rt, result); + } + + private static void EmitGetTpidrroEl0(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + + Operand result = context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrroEl0Offset()))); + + SetIntOrZR(context, op.Rt, result); + } + + private static void EmitSetNzcv(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + Operand nzcv = GetIntOrZR(context, op.Rt); + nzcv = context.ConvertI64ToI32(nzcv); + + SetFlag(context, PState.VFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const((int)PState.VFlag)), Const(1))); + SetFlag(context, PState.CFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const((int)PState.CFlag)), Const(1))); + SetFlag(context, PState.ZFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const((int)PState.ZFlag)), Const(1))); + SetFlag(context, PState.NFlag, context.BitwiseAnd(context.ShiftRightUI(nzcv, Const((int)PState.NFlag)), Const(1))); + } + + private static void EmitSetFpcr(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + Operand fpcr = GetIntOrZR(context, op.Rt); + fpcr = context.ConvertI64ToI32(fpcr); + + for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++) + { + if (FPCR.Mask.HasFlag((FPCR)(1u << flag))) + { + SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpcr, Const(flag)), Const(1))); + } + } + + context.UpdateArmFpMode(); + } + + private static void EmitSetFpsr(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + context.ClearQcFlagIfModified(); + + Operand fpsr = GetIntOrZR(context, op.Rt); + fpsr = context.ConvertI64ToI32(fpsr); + + for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++) + { + if (FPSR.Mask.HasFlag((FPSR)(1u << flag))) + { + SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpsr, Const(flag)), Const(1))); + } + } + + context.UpdateArmFpMode(); + } + + private static void EmitSetTpidrEl0(ArmEmitterContext context) + { + OpCodeSystem op = (OpCodeSystem)context.CurrOp; + + Operand value = GetIntOrZR(context, op.Rt); + + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + + context.Store(context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())), value); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstEmitSystem32.cs b/src/ARMeilleure/Instructions/InstEmitSystem32.cs new file mode 100644 index 00000000..f2732c99 --- /dev/null +++ b/src/ARMeilleure/Instructions/InstEmitSystem32.cs @@ -0,0 +1,351 @@ +using ARMeilleure.Decoders; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using System.Reflection; + +using static ARMeilleure.Instructions.InstEmitHelper; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Instructions +{ + static partial class InstEmit32 + { + public static void Mcr(ArmEmitterContext context) + { + OpCode32System op = (OpCode32System)context.CurrOp; + + if (op.Coproc != 15 || op.Opc1 != 0) + { + InstEmit.Und(context); + + return; + } + + switch (op.CRn) + { + case 13: // Process and Thread Info. + if (op.CRm != 0) + { + throw new NotImplementedException($"Unknown MRC CRm 0x{op.CRm:X} at 0x{op.Address:X} (0x{op.RawOpCode:X})."); + } + + switch (op.Opc2) + { + case 2: + EmitSetTpidrEl0(context); return; + + default: + throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X})."); + } + + case 7: + switch (op.CRm) // Cache and Memory barrier. + { + case 10: + switch (op.Opc2) + { + case 5: // Data Memory Barrier Register. + return; // No-op. + + default: + throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X16} at 0x{op.Address:X16} (0x{op.RawOpCode:X})."); + } + + default: + throw new NotImplementedException($"Unknown MRC CRm 0x{op.CRm:X16} at 0x{op.Address:X16} (0x{op.RawOpCode:X})."); + } + + default: + throw new NotImplementedException($"Unknown MRC 0x{op.RawOpCode:X8} at 0x{op.Address:X16}."); + } + } + + public static void Mrc(ArmEmitterContext context) + { + OpCode32System op = (OpCode32System)context.CurrOp; + + if (op.Coproc != 15 || op.Opc1 != 0) + { + InstEmit.Und(context); + + return; + } + + Operand result; + + switch (op.CRn) + { + case 13: // Process and Thread Info. + if (op.CRm != 0) + { + throw new NotImplementedException($"Unknown MRC CRm 0x{op.CRm:X} at 0x{op.Address:X} (0x{op.RawOpCode:X})."); + } + + switch (op.Opc2) + { + case 2: + result = EmitGetTpidrEl0(context); break; + + case 3: + result = EmitGetTpidrroEl0(context); break; + + default: + throw new NotImplementedException($"Unknown MRC Opc2 0x{op.Opc2:X} at 0x{op.Address:X} (0x{op.RawOpCode:X})."); + } + + break; + + default: + throw new NotImplementedException($"Unknown MRC 0x{op.RawOpCode:X} at 0x{op.Address:X}."); + } + + if (op.Rt == RegisterAlias.Aarch32Pc) + { + // Special behavior: copy NZCV flags into APSR. + EmitSetNzcv(context, result); + + return; + } + else + { + SetIntA32(context, op.Rt, result); + } + } + + public static void Mrrc(ArmEmitterContext context) + { + OpCode32System op = (OpCode32System)context.CurrOp; + + if (op.Coproc != 15) + { + InstEmit.Und(context); + + return; + } + + int opc = op.MrrcOp; + + MethodInfo info; + + switch (op.CRm) + { + case 14: // Timer. + switch (opc) + { + case 0: + info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntpctEl0)); break; + + default: + throw new NotImplementedException($"Unknown MRRC Opc1 0x{opc:X} at 0x{op.Address:X} (0x{op.RawOpCode:X})."); + } + + break; + + default: + throw new NotImplementedException($"Unknown MRRC 0x{op.RawOpCode:X} at 0x{op.Address:X}."); + } + + Operand result = context.Call(info); + + SetIntA32(context, op.Rt, context.ConvertI64ToI32(result)); + SetIntA32(context, op.CRn, context.ConvertI64ToI32(context.ShiftRightUI(result, Const(32)))); + } + + public static void Mrs(ArmEmitterContext context) + { + OpCode32Mrs op = (OpCode32Mrs)context.CurrOp; + + if (op.R) + { + throw new NotImplementedException("SPSR"); + } + else + { + Operand spsr = context.ShiftLeft(GetFlag(PState.VFlag), Const((int)PState.VFlag)); + spsr = context.BitwiseOr(spsr, context.ShiftLeft(GetFlag(PState.CFlag), Const((int)PState.CFlag))); + spsr = context.BitwiseOr(spsr, context.ShiftLeft(GetFlag(PState.ZFlag), Const((int)PState.ZFlag))); + spsr = context.BitwiseOr(spsr, context.ShiftLeft(GetFlag(PState.NFlag), Const((int)PState.NFlag))); + spsr = context.BitwiseOr(spsr, context.ShiftLeft(GetFlag(PState.QFlag), Const((int)PState.QFlag))); + + // TODO: Remaining flags. + + SetIntA32(context, op.Rd, spsr); + } + } + + public static void Msr(ArmEmitterContext context) + { + OpCode32MsrReg op = (OpCode32MsrReg)context.CurrOp; + + if (op.R) + { + throw new NotImplementedException("SPSR"); + } + else + { + if ((op.Mask & 8) != 0) + { + Operand value = GetIntA32(context, op.Rn); + + EmitSetNzcv(context, value); + + Operand q = context.BitwiseAnd(context.ShiftRightUI(value, Const((int)PState.QFlag)), Const(1)); + + SetFlag(context, PState.QFlag, q); + } + + if ((op.Mask & 4) != 0) + { + throw new NotImplementedException("APSR_g"); + } + + if ((op.Mask & 2) != 0) + { + throw new NotImplementedException("CPSR_x"); + } + + if ((op.Mask & 1) != 0) + { + throw new NotImplementedException("CPSR_c"); + } + } + } + + public static void Nop(ArmEmitterContext context) { } + + public static void Vmrs(ArmEmitterContext context) + { + OpCode32SimdSpecial op = (OpCode32SimdSpecial)context.CurrOp; + + if (op.Rt == RegisterAlias.Aarch32Pc && op.Sreg == 0b0001) + { + // Special behavior: copy NZCV flags into APSR. + SetFlag(context, PState.VFlag, GetFpFlag(FPState.VFlag)); + SetFlag(context, PState.CFlag, GetFpFlag(FPState.CFlag)); + SetFlag(context, PState.ZFlag, GetFpFlag(FPState.ZFlag)); + SetFlag(context, PState.NFlag, GetFpFlag(FPState.NFlag)); + + return; + } + + switch (op.Sreg) + { + case 0b0000: // FPSID + throw new NotImplementedException("Supervisor Only"); + case 0b0001: // FPSCR + EmitGetFpscr(context); return; + case 0b0101: // MVFR2 + throw new NotImplementedException("MVFR2"); + case 0b0110: // MVFR1 + throw new NotImplementedException("MVFR1"); + case 0b0111: // MVFR0 + throw new NotImplementedException("MVFR0"); + case 0b1000: // FPEXC + throw new NotImplementedException("Supervisor Only"); + default: + throw new NotImplementedException($"Unknown VMRS 0x{op.RawOpCode:X} at 0x{op.Address:X}."); + } + } + + public static void Vmsr(ArmEmitterContext context) + { + OpCode32SimdSpecial op = (OpCode32SimdSpecial)context.CurrOp; + + switch (op.Sreg) + { + case 0b0000: // FPSID + throw new NotImplementedException("Supervisor Only"); + case 0b0001: // FPSCR + EmitSetFpscr(context); return; + case 0b0101: // MVFR2 + throw new NotImplementedException("MVFR2"); + case 0b0110: // MVFR1 + throw new NotImplementedException("MVFR1"); + case 0b0111: // MVFR0 + throw new NotImplementedException("MVFR0"); + case 0b1000: // FPEXC + throw new NotImplementedException("Supervisor Only"); + default: + throw new NotImplementedException($"Unknown VMSR 0x{op.RawOpCode:X} at 0x{op.Address:X}."); + } + } + + private static void EmitSetNzcv(ArmEmitterContext context, Operand t) + { + Operand v = context.BitwiseAnd(context.ShiftRightUI(t, Const((int)PState.VFlag)), Const(1)); + Operand c = context.BitwiseAnd(context.ShiftRightUI(t, Const((int)PState.CFlag)), Const(1)); + Operand z = context.BitwiseAnd(context.ShiftRightUI(t, Const((int)PState.ZFlag)), Const(1)); + Operand n = context.BitwiseAnd(context.ShiftRightUI(t, Const((int)PState.NFlag)), Const(1)); + + SetFlag(context, PState.VFlag, v); + SetFlag(context, PState.CFlag, c); + SetFlag(context, PState.ZFlag, z); + SetFlag(context, PState.NFlag, n); + } + + private static void EmitGetFpscr(ArmEmitterContext context) + { + OpCode32SimdSpecial op = (OpCode32SimdSpecial)context.CurrOp; + + Operand fpscr = Const(0); + + for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++) + { + if (FPSCR.Mask.HasFlag((FPSCR)(1u << flag))) + { + fpscr = context.BitwiseOr(fpscr, context.ShiftLeft(GetFpFlag((FPState)flag), Const(flag))); + } + } + + SetIntA32(context, op.Rt, fpscr); + } + + private static void EmitSetFpscr(ArmEmitterContext context) + { + OpCode32SimdSpecial op = (OpCode32SimdSpecial)context.CurrOp; + + Operand fpscr = GetIntA32(context, op.Rt); + + for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++) + { + if (FPSCR.Mask.HasFlag((FPSCR)(1u << flag))) + { + SetFpFlag(context, (FPState)flag, context.BitwiseAnd(context.ShiftRightUI(fpscr, Const(flag)), Const(1))); + } + } + + context.UpdateArmFpMode(); + } + + private static Operand EmitGetTpidrEl0(ArmEmitterContext context) + { + OpCode32System op = (OpCode32System)context.CurrOp; + + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + + return context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset()))); + } + + private static Operand EmitGetTpidrroEl0(ArmEmitterContext context) + { + OpCode32System op = (OpCode32System)context.CurrOp; + + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + + return context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrroEl0Offset()))); + } + + private static void EmitSetTpidrEl0(ArmEmitterContext context) + { + OpCode32System op = (OpCode32System)context.CurrOp; + + Operand value = GetIntA32(context, op.Rt); + + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + + context.Store(context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())), context.ZeroExtend32(OperandType.I64, value)); + } + } +} diff --git a/src/ARMeilleure/Instructions/InstName.cs b/src/ARMeilleure/Instructions/InstName.cs new file mode 100644 index 00000000..fd71d92e --- /dev/null +++ b/src/ARMeilleure/Instructions/InstName.cs @@ -0,0 +1,685 @@ +namespace ARMeilleure.Instructions +{ + enum InstName + { + // Base (AArch64) + Adc, + Adcs, + Add, + Adds, + Adr, + Adrp, + And, + Ands, + Asrv, + B, + B_Cond, + Bfm, + Bic, + Bics, + Bl, + Blr, + Br, + Brk, + Cbnz, + Cbz, + Ccmn, + Ccmp, + Clrex, + Cls, + Clz, + Crc32b, + Crc32h, + Crc32w, + Crc32x, + Crc32cb, + Crc32ch, + Crc32cw, + Crc32cx, + Csdb, + Csel, + Csinc, + Csinv, + Csneg, + Dmb, + Dsb, + Eon, + Eor, + Esb, + Extr, + Hint, + Isb, + It, + Ldar, + Ldaxp, + Ldaxr, + Ldp, + Ldr, + Ldr_Literal, + Ldrs, + Ldxr, + Ldxp, + Lslv, + Lsrv, + Madd, + Movk, + Movn, + Movz, + Mrs, + Msr, + Msub, + Nop, + Orn, + Orr, + Prfm, + Rbit, + Ret, + Rev16, + Rev32, + Rev64, + Rorv, + Sbc, + Sbcs, + Sbfm, + Sdiv, + Sel, + Sev, + Sevl, + Shsub8, + Smaddl, + Smsubl, + Smulh, + Smull, + Smulw_, + Ssat, + Ssat16, + Stlr, + Stlxp, + Stlxr, + Stp, + Str, + Stxp, + Stxr, + Sub, + Subs, + Svc, + Sxtb, + Sxth, + Sys, + Tbnz, + Tbz, + Tsb, + Ubfm, + Udiv, + Umaddl, + Umsubl, + Umulh, + Und, + Wfe, + Wfi, + Yield, + + // FP & SIMD (AArch64) + Abs_S, + Abs_V, + Add_S, + Add_V, + Addhn_V, + Addp_S, + Addp_V, + Addv_V, + Aesd_V, + Aese_V, + Aesimc_V, + Aesmc_V, + And_V, + Bic_V, + Bic_Vi, + Bif_V, + Bit_V, + Bsl_V, + Cls_V, + Clz_V, + Cmeq_S, + Cmeq_V, + Cmge_S, + Cmge_V, + Cmgt_S, + Cmgt_V, + Cmhi_S, + Cmhi_V, + Cmhs_S, + Cmhs_V, + Cmle_S, + Cmle_V, + Cmlt_S, + Cmlt_V, + Cmtst_S, + Cmtst_V, + Cnt_V, + Dup_Gp, + Dup_S, + Dup_V, + Eor_V, + Ext_V, + Fabd_S, + Fabd_V, + Fabs_S, + Fabs_V, + Facge_S, + Facge_V, + Facgt_S, + Facgt_V, + Fadd_S, + Fadd_V, + Faddp_S, + Faddp_V, + Fccmp_S, + Fccmpe_S, + Fcmeq_S, + Fcmeq_V, + Fcmge_S, + Fcmge_V, + Fcmgt_S, + Fcmgt_V, + Fcmle_S, + Fcmle_V, + Fcmlt_S, + Fcmlt_V, + Fcmp_S, + Fcmpe_S, + Fcsel_S, + Fcvt_S, + Fcvtas_Gp, + Fcvtas_S, + Fcvtas_V, + Fcvtau_Gp, + Fcvtau_S, + Fcvtau_V, + Fcvtl_V, + Fcvtms_Gp, + Fcvtms_V, + Fcvtmu_Gp, + Fcvtn_V, + Fcvtns_Gp, + Fcvtns_S, + Fcvtns_V, + Fcvtnu_S, + Fcvtnu_V, + Fcvtps_Gp, + Fcvtpu_Gp, + Fcvtzs_Gp, + Fcvtzs_Gp_Fixed, + Fcvtzs_S, + Fcvtzs_V, + Fcvtzs_V_Fixed, + Fcvtzu_Gp, + Fcvtzu_Gp_Fixed, + Fcvtzu_S, + Fcvtzu_V, + Fcvtzu_V_Fixed, + Fdiv_S, + Fdiv_V, + Fmadd_S, + Fmax_S, + Fmax_V, + Fmaxnm_S, + Fmaxnm_V, + Fmaxnmp_S, + Fmaxnmp_V, + Fmaxnmv_V, + Fmaxp_V, + Fmaxv_V, + Fmin_S, + Fmin_V, + Fminnm_S, + Fminnm_V, + Fminnmp_S, + Fminnmp_V, + Fminnmv_V, + Fminp_V, + Fminv_V, + Fmla_Se, + Fmla_V, + Fmla_Ve, + Fmls_Se, + Fmls_V, + Fmls_Ve, + Fmov_S, + Fmov_Si, + Fmov_Vi, + Fmov_Ftoi, + Fmov_Itof, + Fmov_Ftoi1, + Fmov_Itof1, + Fmsub_S, + Fmul_S, + Fmul_Se, + Fmul_V, + Fmul_Ve, + Fmulx_S, + Fmulx_Se, + Fmulx_V, + Fmulx_Ve, + Fneg_S, + Fneg_V, + Fnmadd_S, + Fnmsub_S, + Fnmul_S, + Frecpe_S, + Frecpe_V, + Frecps_S, + Frecps_V, + Frecpx_S, + Frinta_S, + Frinta_V, + Frinti_S, + Frinti_V, + Frintm_S, + Frintm_V, + Frintn_S, + Frintn_V, + Frintp_S, + Frintp_V, + Frintx_S, + Frintx_V, + Frintz_S, + Frintz_V, + Frsqrte_S, + Frsqrte_V, + Frsqrts_S, + Frsqrts_V, + Fsqrt_S, + Fsqrt_V, + Fsub_S, + Fsub_V, + Ins_Gp, + Ins_V, + Ld__Vms, + Ld__Vss, + Mla_V, + Mla_Ve, + Mls_V, + Mls_Ve, + Movi_V, + Mul_V, + Mul_Ve, + Mvni_V, + Neg_S, + Neg_V, + Not_V, + Orn_V, + Orr_V, + Orr_Vi, + Pmull_V, + Raddhn_V, + Rbit_V, + Rev16_V, + Rev32_V, + Rev64_V, + Rshrn_V, + Rsubhn_V, + Saba_V, + Sabal_V, + Sabd_V, + Sabdl_V, + Sadalp_V, + Saddl_V, + Saddlp_V, + Saddlv_V, + Saddw_V, + Scvtf_Gp, + Scvtf_Gp_Fixed, + Scvtf_S, + Scvtf_S_Fixed, + Scvtf_V, + Scvtf_V_Fixed, + Sha1c_V, + Sha1h_V, + Sha1m_V, + Sha1p_V, + Sha1su0_V, + Sha1su1_V, + Sha256h_V, + Sha256h2_V, + Sha256su0_V, + Sha256su1_V, + Shadd_V, + Shl_S, + Shl_V, + Shll_V, + Shrn_V, + Shsub_V, + Sli_S, + Sli_V, + Smax_V, + Smaxp_V, + Smaxv_V, + Smin_V, + Sminp_V, + Sminv_V, + Smlal_V, + Smlal_Ve, + Smlsl_V, + Smlsl_Ve, + Smov_S, + Smull_V, + Smull_Ve, + Sqabs_S, + Sqabs_V, + Sqadd_S, + Sqadd_V, + Sqdmulh_S, + Sqdmulh_V, + Sqdmulh_Ve, + Sqneg_S, + Sqneg_V, + Sqrdmulh_S, + Sqrdmulh_V, + Sqrdmulh_Ve, + Sqrshl_V, + Sqrshrn_S, + Sqrshrn_V, + Sqrshrun_S, + Sqrshrun_V, + Sqshl_V, + Sqshrn_S, + Sqshrn_V, + Sqshrun_S, + Sqshrun_V, + Sqsub_S, + Sqsub_V, + Sqxtn_S, + Sqxtn_V, + Sqxtun_S, + Sqxtun_V, + Srhadd_V, + Sri_S, + Sri_V, + Srshl_V, + Srshr_S, + Srshr_V, + Srsra_S, + Srsra_V, + Sshl_S, + Sshl_V, + Sshll_V, + Sshr_S, + Sshr_V, + Ssra_S, + Ssra_V, + Ssubl_V, + Ssubw_V, + St__Vms, + St__Vss, + Sub_S, + Sub_V, + Subhn_V, + Suqadd_S, + Suqadd_V, + Tbl_V, + Tbx_V, + Trn1_V, + Trn2_V, + Uaba_V, + Uabal_V, + Uabd_V, + Uabdl_V, + Uadalp_V, + Uaddl_V, + Uaddlp_V, + Uaddlv_V, + Uaddw_V, + Ucvtf_Gp, + Ucvtf_Gp_Fixed, + Ucvtf_S, + Ucvtf_S_Fixed, + Ucvtf_V, + Ucvtf_V_Fixed, + Uhadd_V, + Uhsub_V, + Umax_V, + Umaxp_V, + Umaxv_V, + Umin_V, + Uminp_V, + Uminv_V, + Umlal_V, + Umlal_Ve, + Umlsl_V, + Umlsl_Ve, + Umov_S, + Umull_V, + Umull_Ve, + Uqadd_S, + Uqadd_V, + Uqrshl_V, + Uqrshrn_S, + Uqrshrn_V, + Uqshl_V, + Uqshrn_S, + Uqshrn_V, + Uqsub_S, + Uqsub_V, + Uqxtn_S, + Uqxtn_V, + Urhadd_V, + Urshl_V, + Urshr_S, + Urshr_V, + Ursra_S, + Ursra_V, + Ushl_S, + Ushl_V, + Ushll_V, + Ushr_S, + Ushr_V, + Usqadd_S, + Usqadd_V, + Usra_S, + Usra_V, + Usubl_V, + Usubw_V, + Uzp1_V, + Uzp2_V, + Xtn_V, + Zip1_V, + Zip2_V, + + // Base (AArch32) + Bfc, + Bfi, + Blx, + Bx, + Cmp, + Cmn, + Movt, + Mul, + Lda, + Ldab, + Ldaex, + Ldaexb, + Ldaexd, + Ldaexh, + Ldah, + Ldm, + Ldrb, + Ldrd, + Ldrex, + Ldrexb, + Ldrexd, + Ldrexh, + Ldrh, + Ldrsb, + Ldrsh, + Mcr, + Mla, + Mls, + Mov, + Mrc, + Mrrc, + Mvn, + Pkh, + Pld, + Pop, + Push, + Rev, + Revsh, + Rsb, + Rsc, + Sadd8, + Sbfx, + Shadd8, + Smla__, + Smlal, + Smlal__, + Smlaw_, + Smmla, + Smmls, + Smul__, + Smmul, + Ssub8, + Stl, + Stlb, + Stlex, + Stlexb, + Stlexd, + Stlexh, + Stlh, + Stm, + Strb, + Strd, + Strex, + Strexb, + Strexd, + Strexh, + Strh, + Sxtb16, + Tbb, + Tbh, + Teq, + Trap, + Tst, + Uadd8, + Ubfx, + Uhadd8, + Uhsub8, + Umaal, + Umlal, + Umull, + Usat, + Usat16, + Usub8, + Uxtb, + Uxtb16, + Uxth, + + // FP & SIMD (AArch32) + Vabd, + Vabdl, + Vabs, + Vadd, + Vaddl, + Vaddw, + Vand, + Vbic, + Vbif, + Vbit, + Vbsl, + Vceq, + Vcge, + Vcgt, + Vcle, + Vclt, + Vcmp, + Vcmpe, + Vcnt, + Vcvt, + Vdiv, + Vdup, + Veor, + Vext, + Vfma, + Vfms, + Vfnma, + Vfnms, + Vhadd, + Vld1, + Vld2, + Vld3, + Vld4, + Vldm, + Vldr, + Vmax, + Vmaxnm, + Vmin, + Vminnm, + Vmla, + Vmlal, + Vmls, + Vmlsl, + Vmov, + Vmovl, + Vmovn, + Vmrs, + Vmsr, + Vmul, + Vmull, + Vmvn, + Vneg, + Vnmul, + Vnmla, + Vnmls, + Vorn, + Vorr, + Vpadd, + Vpaddl, + Vpmax, + Vpmin, + Vqadd, + Vqdmulh, + Vqmovn, + Vqmovun, + Vqrshrn, + Vqrshrun, + Vqshrn, + Vqshrun, + Vqsub, + Vrev, + Vrhadd, + Vrint, + Vrinta, + Vrintm, + Vrintn, + Vrintp, + Vrintx, + Vrshr, + Vrshrn, + Vsel, + Vshl, + Vshll, + Vshr, + Vshrn, + Vst1, + Vst2, + Vst3, + Vst4, + Vstm, + Vstr, + Vsqrt, + Vrecpe, + Vrecps, + Vrsqrte, + Vrsqrts, + Vrsra, + Vsra, + Vsub, + Vsubl, + Vsubw, + Vtbl, + Vtrn, + Vtst, + Vuzp, + Vzip, + } +} diff --git a/src/ARMeilleure/Instructions/NativeInterface.cs b/src/ARMeilleure/Instructions/NativeInterface.cs new file mode 100644 index 00000000..2c35387a --- /dev/null +++ b/src/ARMeilleure/Instructions/NativeInterface.cs @@ -0,0 +1,195 @@ +using ARMeilleure.Memory; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; + +namespace ARMeilleure.Instructions +{ + static class NativeInterface + { + private class ThreadContext + { + public ExecutionContext Context { get; } + public IMemoryManager Memory { get; } + public Translator Translator { get; } + + public ThreadContext(ExecutionContext context, IMemoryManager memory, Translator translator) + { + Context = context; + Memory = memory; + Translator = translator; + } + } + + [ThreadStatic] + private static ThreadContext Context; + + public static void RegisterThread(ExecutionContext context, IMemoryManager memory, Translator translator) + { + Context = new ThreadContext(context, memory, translator); + } + + public static void UnregisterThread() + { + Context = null; + } + + public static void Break(ulong address, int imm) + { + Statistics.PauseTimer(); + + GetContext().OnBreak(address, imm); + + Statistics.ResumeTimer(); + } + + public static void SupervisorCall(ulong address, int imm) + { + Statistics.PauseTimer(); + + GetContext().OnSupervisorCall(address, imm); + + Statistics.ResumeTimer(); + } + + public static void Undefined(ulong address, int opCode) + { + Statistics.PauseTimer(); + + GetContext().OnUndefined(address, opCode); + + Statistics.ResumeTimer(); + } + + #region "System registers" + public static ulong GetCtrEl0() + { + return (ulong)GetContext().CtrEl0; + } + + public static ulong GetDczidEl0() + { + return (ulong)GetContext().DczidEl0; + } + + public static ulong GetCntfrqEl0() + { + return GetContext().CntfrqEl0; + } + + public static ulong GetCntpctEl0() + { + return GetContext().CntpctEl0; + } + + public static ulong GetCntvctEl0() + { + return GetContext().CntvctEl0; + } + #endregion + + #region "Read" + public static byte ReadByte(ulong address) + { + return GetMemoryManager().ReadTracked(address); + } + + public static ushort ReadUInt16(ulong address) + { + return GetMemoryManager().ReadTracked(address); + } + + public static uint ReadUInt32(ulong address) + { + return GetMemoryManager().ReadTracked(address); + } + + public static ulong ReadUInt64(ulong address) + { + return GetMemoryManager().ReadTracked(address); + } + + public static V128 ReadVector128(ulong address) + { + return GetMemoryManager().ReadTracked(address); + } + #endregion + + #region "Write" + public static void WriteByte(ulong address, byte value) + { + GetMemoryManager().Write(address, value); + } + + public static void WriteUInt16(ulong address, ushort value) + { + GetMemoryManager().Write(address, value); + } + + public static void WriteUInt32(ulong address, uint value) + { + GetMemoryManager().Write(address, value); + } + + public static void WriteUInt64(ulong address, ulong value) + { + GetMemoryManager().Write(address, value); + } + + public static void WriteVector128(ulong address, V128 value) + { + GetMemoryManager().Write(address, value); + } + #endregion + + public static void EnqueueForRejit(ulong address) + { + Context.Translator.EnqueueForRejit(address, GetContext().ExecutionMode); + } + + public static void SignalMemoryTracking(ulong address, ulong size, bool write) + { + GetMemoryManager().SignalMemoryTracking(address, size, write); + } + + public static void ThrowInvalidMemoryAccess(ulong address) + { + throw new InvalidAccessException(address); + } + + public static ulong GetFunctionAddress(ulong address) + { + TranslatedFunction function = Context.Translator.GetOrTranslate(address, GetContext().ExecutionMode); + + return (ulong)function.FuncPointer.ToInt64(); + } + + public static void InvalidateCacheLine(ulong address) + { + Context.Translator.InvalidateJitCacheRegion(address, InstEmit.DczSizeInBytes); + } + + public static bool CheckSynchronization() + { + Statistics.PauseTimer(); + + ExecutionContext context = GetContext(); + + context.CheckInterrupt(); + + Statistics.ResumeTimer(); + + return context.Running; + } + + public static ExecutionContext GetContext() + { + return Context.Context; + } + + public static IMemoryManager GetMemoryManager() + { + return Context.Memory; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Instructions/SoftFallback.cs b/src/ARMeilleure/Instructions/SoftFallback.cs new file mode 100644 index 00000000..06d76a67 --- /dev/null +++ b/src/ARMeilleure/Instructions/SoftFallback.cs @@ -0,0 +1,624 @@ +using ARMeilleure.State; +using System; + +namespace ARMeilleure.Instructions +{ + static class SoftFallback + { +#region "ShrImm64" + public static long SignedShrImm64(long value, long roundConst, int shift) + { + if (roundConst == 0L) + { + if (shift <= 63) + { + return value >> shift; + } + else /* if (shift == 64) */ + { + if (value < 0L) + { + return -1L; + } + else /* if (value >= 0L) */ + { + return 0L; + } + } + } + else /* if (roundConst == 1L << (shift - 1)) */ + { + if (shift <= 63) + { + long add = value + roundConst; + + if ((~value & (value ^ add)) < 0L) + { + return (long)((ulong)add >> shift); + } + else + { + return add >> shift; + } + } + else /* if (shift == 64) */ + { + return 0L; + } + } + } + + public static ulong UnsignedShrImm64(ulong value, long roundConst, int shift) + { + if (roundConst == 0L) + { + if (shift <= 63) + { + return value >> shift; + } + else /* if (shift == 64) */ + { + return 0UL; + } + } + else /* if (roundConst == 1L << (shift - 1)) */ + { + ulong add = value + (ulong)roundConst; + + if ((add < value) && (add < (ulong)roundConst)) + { + if (shift <= 63) + { + return (add >> shift) | (0x8000000000000000UL >> (shift - 1)); + } + else /* if (shift == 64) */ + { + return 1UL; + } + } + else + { + if (shift <= 63) + { + return add >> shift; + } + else /* if (shift == 64) */ + { + return 0UL; + } + } + } + } +#endregion + +#region "Saturation" + public static int SatF32ToS32(float value) + { + if (float.IsNaN(value)) return 0; + + return value >= int.MaxValue ? int.MaxValue : + value <= int.MinValue ? int.MinValue : (int)value; + } + + public static long SatF32ToS64(float value) + { + if (float.IsNaN(value)) return 0; + + return value >= long.MaxValue ? long.MaxValue : + value <= long.MinValue ? long.MinValue : (long)value; + } + + public static uint SatF32ToU32(float value) + { + if (float.IsNaN(value)) return 0; + + return value >= uint.MaxValue ? uint.MaxValue : + value <= uint.MinValue ? uint.MinValue : (uint)value; + } + + public static ulong SatF32ToU64(float value) + { + if (float.IsNaN(value)) return 0; + + return value >= ulong.MaxValue ? ulong.MaxValue : + value <= ulong.MinValue ? ulong.MinValue : (ulong)value; + } + + public static int SatF64ToS32(double value) + { + if (double.IsNaN(value)) return 0; + + return value >= int.MaxValue ? int.MaxValue : + value <= int.MinValue ? int.MinValue : (int)value; + } + + public static long SatF64ToS64(double value) + { + if (double.IsNaN(value)) return 0; + + return value >= long.MaxValue ? long.MaxValue : + value <= long.MinValue ? long.MinValue : (long)value; + } + + public static uint SatF64ToU32(double value) + { + if (double.IsNaN(value)) return 0; + + return value >= uint.MaxValue ? uint.MaxValue : + value <= uint.MinValue ? uint.MinValue : (uint)value; + } + + public static ulong SatF64ToU64(double value) + { + if (double.IsNaN(value)) return 0; + + return value >= ulong.MaxValue ? ulong.MaxValue : + value <= ulong.MinValue ? ulong.MinValue : (ulong)value; + } +#endregion + +#region "Count" + public static ulong CountLeadingSigns(ulong value, int size) // size is 8, 16, 32 or 64 (SIMD&FP or Base Inst.). + { + value ^= value >> 1; + + int highBit = size - 2; + + for (int bit = highBit; bit >= 0; bit--) + { + if (((int)(value >> bit) & 0b1) != 0) + { + return (ulong)(highBit - bit); + } + } + + return (ulong)(size - 1); + } + + private static ReadOnlySpan ClzNibbleTbl => new byte[] { 4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 }; + + public static ulong CountLeadingZeros(ulong value, int size) // size is 8, 16, 32 or 64 (SIMD&FP or Base Inst.). + { + if (value == 0ul) + { + return (ulong)size; + } + + int nibbleIdx = size; + int preCount, count = 0; + + do + { + nibbleIdx -= 4; + preCount = ClzNibbleTbl[(int)(value >> nibbleIdx) & 0b1111]; + count += preCount; + } + while (preCount == 4); + + return (ulong)count; + } +#endregion + +#region "Table" + public static V128 Tbl1(V128 vector, int bytes, V128 tb0) + { + return TblOrTbx(default, vector, bytes, tb0); + } + + public static V128 Tbl2(V128 vector, int bytes, V128 tb0, V128 tb1) + { + return TblOrTbx(default, vector, bytes, tb0, tb1); + } + + public static V128 Tbl3(V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2) + { + return TblOrTbx(default, vector, bytes, tb0, tb1, tb2); + } + + public static V128 Tbl4(V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2, V128 tb3) + { + return TblOrTbx(default, vector, bytes, tb0, tb1, tb2, tb3); + } + + public static V128 Tbx1(V128 dest, V128 vector, int bytes, V128 tb0) + { + return TblOrTbx(dest, vector, bytes, tb0); + } + + public static V128 Tbx2(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1) + { + return TblOrTbx(dest, vector, bytes, tb0, tb1); + } + + public static V128 Tbx3(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2) + { + return TblOrTbx(dest, vector, bytes, tb0, tb1, tb2); + } + + public static V128 Tbx4(V128 dest, V128 vector, int bytes, V128 tb0, V128 tb1, V128 tb2, V128 tb3) + { + return TblOrTbx(dest, vector, bytes, tb0, tb1, tb2, tb3); + } + + private static V128 TblOrTbx(V128 dest, V128 vector, int bytes, params V128[] tb) + { + byte[] res = new byte[16]; + + if (dest != default) + { + Buffer.BlockCopy(dest.ToArray(), 0, res, 0, bytes); + } + + byte[] table = new byte[tb.Length * 16]; + + for (byte index = 0; index < tb.Length; index++) + { + Buffer.BlockCopy(tb[index].ToArray(), 0, table, index * 16, 16); + } + + byte[] v = vector.ToArray(); + + for (byte index = 0; index < bytes; index++) + { + byte tblIndex = v[index]; + + if (tblIndex < table.Length) + { + res[index] = table[tblIndex]; + } + } + + return new V128(res); + } +#endregion + +#region "Crc32" + private const uint Crc32RevPoly = 0xedb88320; + private const uint Crc32cRevPoly = 0x82f63b78; + + public static uint Crc32b(uint crc, byte value) => Crc32 (crc, Crc32RevPoly, value); + public static uint Crc32h(uint crc, ushort value) => Crc32h(crc, Crc32RevPoly, value); + public static uint Crc32w(uint crc, uint value) => Crc32w(crc, Crc32RevPoly, value); + public static uint Crc32x(uint crc, ulong value) => Crc32x(crc, Crc32RevPoly, value); + + public static uint Crc32cb(uint crc, byte value) => Crc32 (crc, Crc32cRevPoly, value); + public static uint Crc32ch(uint crc, ushort value) => Crc32h(crc, Crc32cRevPoly, value); + public static uint Crc32cw(uint crc, uint value) => Crc32w(crc, Crc32cRevPoly, value); + public static uint Crc32cx(uint crc, ulong value) => Crc32x(crc, Crc32cRevPoly, value); + + private static uint Crc32h(uint crc, uint poly, ushort val) + { + crc = Crc32(crc, poly, (byte)(val >> 0)); + crc = Crc32(crc, poly, (byte)(val >> 8)); + + return crc; + } + + private static uint Crc32w(uint crc, uint poly, uint val) + { + crc = Crc32(crc, poly, (byte)(val >> 0)); + crc = Crc32(crc, poly, (byte)(val >> 8)); + crc = Crc32(crc, poly, (byte)(val >> 16)); + crc = Crc32(crc, poly, (byte)(val >> 24)); + + return crc; + } + + private static uint Crc32x(uint crc, uint poly, ulong val) + { + crc = Crc32(crc, poly, (byte)(val >> 0)); + crc = Crc32(crc, poly, (byte)(val >> 8)); + crc = Crc32(crc, poly, (byte)(val >> 16)); + crc = Crc32(crc, poly, (byte)(val >> 24)); + crc = Crc32(crc, poly, (byte)(val >> 32)); + crc = Crc32(crc, poly, (byte)(val >> 40)); + crc = Crc32(crc, poly, (byte)(val >> 48)); + crc = Crc32(crc, poly, (byte)(val >> 56)); + + return crc; + } + + private static uint Crc32(uint crc, uint poly, byte val) + { + crc ^= val; + + for (int bit = 7; bit >= 0; bit--) + { + uint mask = (uint)(-(int)(crc & 1)); + + crc = (crc >> 1) ^ (poly & mask); + } + + return crc; + } +#endregion + +#region "Aes" + public static V128 Decrypt(V128 value, V128 roundKey) + { + return CryptoHelper.AesInvSubBytes(CryptoHelper.AesInvShiftRows(value ^ roundKey)); + } + + public static V128 Encrypt(V128 value, V128 roundKey) + { + return CryptoHelper.AesSubBytes(CryptoHelper.AesShiftRows(value ^ roundKey)); + } + + public static V128 InverseMixColumns(V128 value) + { + return CryptoHelper.AesInvMixColumns(value); + } + + public static V128 MixColumns(V128 value) + { + return CryptoHelper.AesMixColumns(value); + } +#endregion + +#region "Sha1" + public static V128 HashChoose(V128 hash_abcd, uint hash_e, V128 wk) + { + for (int e = 0; e <= 3; e++) + { + uint t = ShaChoose(hash_abcd.Extract(1), + hash_abcd.Extract(2), + hash_abcd.Extract(3)); + + hash_e += Rol(hash_abcd.Extract(0), 5) + t + wk.Extract(e); + + t = Rol(hash_abcd.Extract(1), 30); + + hash_abcd.Insert(1, t); + + Rol32_160(ref hash_e, ref hash_abcd); + } + + return hash_abcd; + } + + public static uint FixedRotate(uint hash_e) + { + return hash_e.Rol(30); + } + + public static V128 HashMajority(V128 hash_abcd, uint hash_e, V128 wk) + { + for (int e = 0; e <= 3; e++) + { + uint t = ShaMajority(hash_abcd.Extract(1), + hash_abcd.Extract(2), + hash_abcd.Extract(3)); + + hash_e += Rol(hash_abcd.Extract(0), 5) + t + wk.Extract(e); + + t = Rol(hash_abcd.Extract(1), 30); + + hash_abcd.Insert(1, t); + + Rol32_160(ref hash_e, ref hash_abcd); + } + + return hash_abcd; + } + + public static V128 HashParity(V128 hash_abcd, uint hash_e, V128 wk) + { + for (int e = 0; e <= 3; e++) + { + uint t = ShaParity(hash_abcd.Extract(1), + hash_abcd.Extract(2), + hash_abcd.Extract(3)); + + hash_e += Rol(hash_abcd.Extract(0), 5) + t + wk.Extract(e); + + t = Rol(hash_abcd.Extract(1), 30); + + hash_abcd.Insert(1, t); + + Rol32_160(ref hash_e, ref hash_abcd); + } + + return hash_abcd; + } + + public static V128 Sha1SchedulePart1(V128 w0_3, V128 w4_7, V128 w8_11) + { + ulong t2 = w4_7.Extract(0); + ulong t1 = w0_3.Extract(1); + + V128 result = new V128(t1, t2); + + return result ^ (w0_3 ^ w8_11); + } + + public static V128 Sha1SchedulePart2(V128 tw0_3, V128 w12_15) + { + V128 t = tw0_3 ^ (w12_15 >> 32); + + uint tE0 = t.Extract(0); + uint tE1 = t.Extract(1); + uint tE2 = t.Extract(2); + uint tE3 = t.Extract(3); + + return new V128(tE0.Rol(1), tE1.Rol(1), tE2.Rol(1), tE3.Rol(1) ^ tE0.Rol(2)); + } + + private static void Rol32_160(ref uint y, ref V128 x) + { + uint xE3 = x.Extract(3); + + x <<= 32; + x.Insert(0, y); + + y = xE3; + } + + private static uint ShaChoose(uint x, uint y, uint z) + { + return ((y ^ z) & x) ^ z; + } + + private static uint ShaMajority(uint x, uint y, uint z) + { + return (x & y) | ((x | y) & z); + } + + private static uint ShaParity(uint x, uint y, uint z) + { + return x ^ y ^ z; + } + + private static uint Rol(this uint value, int count) + { + return (value << count) | (value >> (32 - count)); + } +#endregion + +#region "Sha256" + public static V128 HashLower(V128 hash_abcd, V128 hash_efgh, V128 wk) + { + return Sha256Hash(hash_abcd, hash_efgh, wk, part1: true); + } + + public static V128 HashUpper(V128 hash_abcd, V128 hash_efgh, V128 wk) + { + return Sha256Hash(hash_abcd, hash_efgh, wk, part1: false); + } + + public static V128 Sha256SchedulePart1(V128 w0_3, V128 w4_7) + { + V128 result = new V128(); + + for (int e = 0; e <= 3; e++) + { + uint elt = (e <= 2 ? w0_3 : w4_7).Extract(e <= 2 ? e + 1 : 0); + + elt = elt.Ror(7) ^ elt.Ror(18) ^ elt.Lsr(3); + + elt += w0_3.Extract(e); + + result.Insert(e, elt); + } + + return result; + } + + public static V128 Sha256SchedulePart2(V128 w0_3, V128 w8_11, V128 w12_15) + { + V128 result = new V128(); + + ulong t1 = w12_15.Extract(1); + + for (int e = 0; e <= 1; e++) + { + uint elt = t1.ULongPart(e); + + elt = elt.Ror(17) ^ elt.Ror(19) ^ elt.Lsr(10); + + elt += w0_3.Extract(e) + w8_11.Extract(e + 1); + + result.Insert(e, elt); + } + + t1 = result.Extract(0); + + for (int e = 2; e <= 3; e++) + { + uint elt = t1.ULongPart(e - 2); + + elt = elt.Ror(17) ^ elt.Ror(19) ^ elt.Lsr(10); + + elt += w0_3.Extract(e) + (e == 2 ? w8_11 : w12_15).Extract(e == 2 ? 3 : 0); + + result.Insert(e, elt); + } + + return result; + } + + private static V128 Sha256Hash(V128 x, V128 y, V128 w, bool part1) + { + for (int e = 0; e <= 3; e++) + { + uint chs = ShaChoose(y.Extract(0), + y.Extract(1), + y.Extract(2)); + + uint maj = ShaMajority(x.Extract(0), + x.Extract(1), + x.Extract(2)); + + uint t1 = y.Extract(3) + ShaHashSigma1(y.Extract(0)) + chs + w.Extract(e); + + uint t2 = t1 + x.Extract(3); + + x.Insert(3, t2); + + t2 = t1 + ShaHashSigma0(x.Extract(0)) + maj; + + y.Insert(3, t2); + + Rol32_256(ref y, ref x); + } + + return part1 ? x : y; + } + + private static void Rol32_256(ref V128 y, ref V128 x) + { + uint yE3 = y.Extract(3); + uint xE3 = x.Extract(3); + + y <<= 32; + x <<= 32; + + y.Insert(0, xE3); + x.Insert(0, yE3); + } + + private static uint ShaHashSigma0(uint x) + { + return x.Ror(2) ^ x.Ror(13) ^ x.Ror(22); + } + + private static uint ShaHashSigma1(uint x) + { + return x.Ror(6) ^ x.Ror(11) ^ x.Ror(25); + } + + private static uint Ror(this uint value, int count) + { + return (value >> count) | (value << (32 - count)); + } + + private static uint Lsr(this uint value, int count) + { + return value >> count; + } + + private static uint ULongPart(this ulong value, int part) + { + return part == 0 + ? (uint)(value & 0xFFFFFFFFUL) + : (uint)(value >> 32); + } +#endregion + + public static V128 PolynomialMult64_128(ulong op1, ulong op2) + { + V128 result = V128.Zero; + + V128 op2_128 = new V128(op2, 0); + + for (int i = 0; i < 64; i++) + { + if (((op1 >> i) & 1) == 1) + { + result ^= op2_128 << i; + } + } + + return result; + } + } +} diff --git a/src/ARMeilleure/Instructions/SoftFloat.cs b/src/ARMeilleure/Instructions/SoftFloat.cs new file mode 100644 index 00000000..9e3db68d --- /dev/null +++ b/src/ARMeilleure/Instructions/SoftFloat.cs @@ -0,0 +1,3480 @@ +using ARMeilleure.State; +using System; +using System.Diagnostics; + +namespace ARMeilleure.Instructions +{ + static class SoftFloat + { + static SoftFloat() + { + RecipEstimateTable = BuildRecipEstimateTable(); + RecipSqrtEstimateTable = BuildRecipSqrtEstimateTable(); + } + + public static readonly byte[] RecipEstimateTable; + public static readonly byte[] RecipSqrtEstimateTable; + + private static byte[] BuildRecipEstimateTable() + { + byte[] tbl = new byte[256]; + + for (int idx = 0; idx < 256; idx++) + { + uint src = (uint)idx + 256u; + + Debug.Assert(256u <= src && src < 512u); + + src = (src << 1) + 1u; + + uint aux = (1u << 19) / src; + + uint dst = (aux + 1u) >> 1; + + Debug.Assert(256u <= dst && dst < 512u); + + tbl[idx] = (byte)(dst - 256u); + } + + return tbl; + } + + private static byte[] BuildRecipSqrtEstimateTable() + { + byte[] tbl = new byte[384]; + + for (int idx = 0; idx < 384; idx++) + { + uint src = (uint)idx + 128u; + + Debug.Assert(128u <= src && src < 512u); + + if (src < 256u) + { + src = (src << 1) + 1u; + } + else + { + src = (src >> 1) << 1; + src = (src + 1u) << 1; + } + + uint aux = 512u; + + while (src * (aux + 1u) * (aux + 1u) < (1u << 28)) + { + aux = aux + 1u; + } + + uint dst = (aux + 1u) >> 1; + + Debug.Assert(256u <= dst && dst < 512u); + + tbl[idx] = (byte)(dst - 256u); + } + + return tbl; + } + + public static void FPProcessException(FPException exc, ExecutionContext context) + { + FPProcessException(exc, context, context.Fpcr); + } + + public static void FPProcessException(FPException exc, ExecutionContext context, FPCR fpcr) + { + int enable = (int)exc + 8; + + if ((fpcr & (FPCR)(1 << enable)) != 0) + { + throw new NotImplementedException("Floating-point trap handling."); + } + else + { + context.Fpsr |= (FPSR)(1 << (int)exc); + } + } + + public static FPRoundingMode GetRoundingMode(this FPCR fpcr) + { + const int RModeShift = 22; + + return (FPRoundingMode)(((uint)fpcr >> RModeShift) & 3u); + } + } + + static class SoftFloat16 + { + public static ushort FPDefaultNaN() + { + return (ushort)0x7E00u; + } + + public static ushort FPInfinity(bool sign) + { + return sign ? (ushort)0xFC00u : (ushort)0x7C00u; + } + + public static ushort FPZero(bool sign) + { + return sign ? (ushort)0x8000u : (ushort)0x0000u; + } + + public static ushort FPMaxNormal(bool sign) + { + return sign ? (ushort)0xFBFFu : (ushort)0x7BFFu; + } + + public static double FPUnpackCv( + this ushort valueBits, + out FPType type, + out bool sign, + ExecutionContext context) + { + sign = (~(uint)valueBits & 0x8000u) == 0u; + + uint exp16 = ((uint)valueBits & 0x7C00u) >> 10; + uint frac16 = (uint)valueBits & 0x03FFu; + + double real; + + if (exp16 == 0u) + { + if (frac16 == 0u) + { + type = FPType.Zero; + real = 0d; + } + else + { + type = FPType.Nonzero; // Subnormal. + real = Math.Pow(2d, -14) * ((double)frac16 * Math.Pow(2d, -10)); + } + } + else if (exp16 == 0x1Fu && (context.Fpcr & FPCR.Ahp) == 0) + { + if (frac16 == 0u) + { + type = FPType.Infinity; + real = Math.Pow(2d, 1000); + } + else + { + type = (~frac16 & 0x0200u) == 0u ? FPType.QNaN : FPType.SNaN; + real = 0d; + } + } + else + { + type = FPType.Nonzero; // Normal. + real = Math.Pow(2d, (int)exp16 - 15) * (1d + (double)frac16 * Math.Pow(2d, -10)); + } + + return sign ? -real : real; + } + + public static ushort FPRoundCv(double real, ExecutionContext context) + { + const int minimumExp = -14; + + const int e = 5; + const int f = 10; + + bool sign; + double mantissa; + + if (real < 0d) + { + sign = true; + mantissa = -real; + } + else + { + sign = false; + mantissa = real; + } + + int exponent = 0; + + while (mantissa < 1d) + { + mantissa *= 2d; + exponent--; + } + + while (mantissa >= 2d) + { + mantissa /= 2d; + exponent++; + } + + uint biasedExp = (uint)Math.Max(exponent - minimumExp + 1, 0); + + if (biasedExp == 0u) + { + mantissa /= Math.Pow(2d, minimumExp - exponent); + } + + uint intMant = (uint)Math.Floor(mantissa * Math.Pow(2d, f)); + double error = mantissa * Math.Pow(2d, f) - (double)intMant; + + if (biasedExp == 0u && (error != 0d || (context.Fpcr & FPCR.Ufe) != 0)) + { + SoftFloat.FPProcessException(FPException.Underflow, context); + } + + bool overflowToInf; + bool roundUp; + + switch (context.Fpcr.GetRoundingMode()) + { + default: + case FPRoundingMode.ToNearest: + roundUp = (error > 0.5d || (error == 0.5d && (intMant & 1u) == 1u)); + overflowToInf = true; + break; + + case FPRoundingMode.TowardsPlusInfinity: + roundUp = (error != 0d && !sign); + overflowToInf = !sign; + break; + + case FPRoundingMode.TowardsMinusInfinity: + roundUp = (error != 0d && sign); + overflowToInf = sign; + break; + + case FPRoundingMode.TowardsZero: + roundUp = false; + overflowToInf = false; + break; + } + + if (roundUp) + { + intMant++; + + if (intMant == 1u << f) + { + biasedExp = 1u; + } + + if (intMant == 1u << (f + 1)) + { + biasedExp++; + intMant >>= 1; + } + } + + ushort resultBits; + + if ((context.Fpcr & FPCR.Ahp) == 0) + { + if (biasedExp >= (1u << e) - 1u) + { + resultBits = overflowToInf ? FPInfinity(sign) : FPMaxNormal(sign); + + SoftFloat.FPProcessException(FPException.Overflow, context); + + error = 1d; + } + else + { + resultBits = (ushort)((sign ? 1u : 0u) << 15 | (biasedExp & 0x1Fu) << 10 | (intMant & 0x03FFu)); + } + } + else + { + if (biasedExp >= 1u << e) + { + resultBits = (ushort)((sign ? 1u : 0u) << 15 | 0x7FFFu); + + SoftFloat.FPProcessException(FPException.InvalidOp, context); + + error = 0d; + } + else + { + resultBits = (ushort)((sign ? 1u : 0u) << 15 | (biasedExp & 0x1Fu) << 10 | (intMant & 0x03FFu)); + } + } + + if (error != 0d) + { + SoftFloat.FPProcessException(FPException.Inexact, context); + } + + return resultBits; + } + } + + static class SoftFloat16_32 + { + public static float FPConvert(ushort valueBits) + { + ExecutionContext context = NativeInterface.GetContext(); + + double real = valueBits.FPUnpackCv(out FPType type, out bool sign, context); + + float result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + if ((context.Fpcr & FPCR.Dn) != 0) + { + result = SoftFloat32.FPDefaultNaN(); + } + else + { + result = FPConvertNaN(valueBits); + } + + if (type == FPType.SNaN) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context); + } + } + else if (type == FPType.Infinity) + { + result = SoftFloat32.FPInfinity(sign); + } + else if (type == FPType.Zero) + { + result = SoftFloat32.FPZero(sign); + } + else + { + result = FPRoundCv(real, context); + } + + return result; + } + + private static float FPRoundCv(double real, ExecutionContext context) + { + const int minimumExp = -126; + + const int e = 8; + const int f = 23; + + bool sign; + double mantissa; + + if (real < 0d) + { + sign = true; + mantissa = -real; + } + else + { + sign = false; + mantissa = real; + } + + int exponent = 0; + + while (mantissa < 1d) + { + mantissa *= 2d; + exponent--; + } + + while (mantissa >= 2d) + { + mantissa /= 2d; + exponent++; + } + + if ((context.Fpcr & FPCR.Fz) != 0 && exponent < minimumExp) + { + context.Fpsr |= FPSR.Ufc; + + return SoftFloat32.FPZero(sign); + } + + uint biasedExp = (uint)Math.Max(exponent - minimumExp + 1, 0); + + if (biasedExp == 0u) + { + mantissa /= Math.Pow(2d, minimumExp - exponent); + } + + uint intMant = (uint)Math.Floor(mantissa * Math.Pow(2d, f)); + double error = mantissa * Math.Pow(2d, f) - (double)intMant; + + if (biasedExp == 0u && (error != 0d || (context.Fpcr & FPCR.Ufe) != 0)) + { + SoftFloat.FPProcessException(FPException.Underflow, context); + } + + bool overflowToInf; + bool roundUp; + + switch (context.Fpcr.GetRoundingMode()) + { + default: + case FPRoundingMode.ToNearest: + roundUp = (error > 0.5d || (error == 0.5d && (intMant & 1u) == 1u)); + overflowToInf = true; + break; + + case FPRoundingMode.TowardsPlusInfinity: + roundUp = (error != 0d && !sign); + overflowToInf = !sign; + break; + + case FPRoundingMode.TowardsMinusInfinity: + roundUp = (error != 0d && sign); + overflowToInf = sign; + break; + + case FPRoundingMode.TowardsZero: + roundUp = false; + overflowToInf = false; + break; + } + + if (roundUp) + { + intMant++; + + if (intMant == 1u << f) + { + biasedExp = 1u; + } + + if (intMant == 1u << (f + 1)) + { + biasedExp++; + intMant >>= 1; + } + } + + float result; + + if (biasedExp >= (1u << e) - 1u) + { + result = overflowToInf ? SoftFloat32.FPInfinity(sign) : SoftFloat32.FPMaxNormal(sign); + + SoftFloat.FPProcessException(FPException.Overflow, context); + + error = 1d; + } + else + { + result = BitConverter.Int32BitsToSingle( + (int)((sign ? 1u : 0u) << 31 | (biasedExp & 0xFFu) << 23 | (intMant & 0x007FFFFFu))); + } + + if (error != 0d) + { + SoftFloat.FPProcessException(FPException.Inexact, context); + } + + return result; + } + + private static float FPConvertNaN(ushort valueBits) + { + return BitConverter.Int32BitsToSingle( + (int)(((uint)valueBits & 0x8000u) << 16 | 0x7FC00000u | ((uint)valueBits & 0x01FFu) << 13)); + } + } + + static class SoftFloat16_64 + { + public static double FPConvert(ushort valueBits) + { + ExecutionContext context = NativeInterface.GetContext(); + + double real = valueBits.FPUnpackCv(out FPType type, out bool sign, context); + + double result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + if ((context.Fpcr & FPCR.Dn) != 0) + { + result = SoftFloat64.FPDefaultNaN(); + } + else + { + result = FPConvertNaN(valueBits); + } + + if (type == FPType.SNaN) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context); + } + } + else if (type == FPType.Infinity) + { + result = SoftFloat64.FPInfinity(sign); + } + else if (type == FPType.Zero) + { + result = SoftFloat64.FPZero(sign); + } + else + { + result = FPRoundCv(real, context); + } + + return result; + } + + private static double FPRoundCv(double real, ExecutionContext context) + { + const int minimumExp = -1022; + + const int e = 11; + const int f = 52; + + bool sign; + double mantissa; + + if (real < 0d) + { + sign = true; + mantissa = -real; + } + else + { + sign = false; + mantissa = real; + } + + int exponent = 0; + + while (mantissa < 1d) + { + mantissa *= 2d; + exponent--; + } + + while (mantissa >= 2d) + { + mantissa /= 2d; + exponent++; + } + + if ((context.Fpcr & FPCR.Fz) != 0 && exponent < minimumExp) + { + context.Fpsr |= FPSR.Ufc; + + return SoftFloat64.FPZero(sign); + } + + uint biasedExp = (uint)Math.Max(exponent - minimumExp + 1, 0); + + if (biasedExp == 0u) + { + mantissa /= Math.Pow(2d, minimumExp - exponent); + } + + ulong intMant = (ulong)Math.Floor(mantissa * Math.Pow(2d, f)); + double error = mantissa * Math.Pow(2d, f) - (double)intMant; + + if (biasedExp == 0u && (error != 0d || (context.Fpcr & FPCR.Ufe) != 0)) + { + SoftFloat.FPProcessException(FPException.Underflow, context); + } + + bool overflowToInf; + bool roundUp; + + switch (context.Fpcr.GetRoundingMode()) + { + default: + case FPRoundingMode.ToNearest: + roundUp = (error > 0.5d || (error == 0.5d && (intMant & 1u) == 1u)); + overflowToInf = true; + break; + + case FPRoundingMode.TowardsPlusInfinity: + roundUp = (error != 0d && !sign); + overflowToInf = !sign; + break; + + case FPRoundingMode.TowardsMinusInfinity: + roundUp = (error != 0d && sign); + overflowToInf = sign; + break; + + case FPRoundingMode.TowardsZero: + roundUp = false; + overflowToInf = false; + break; + } + + if (roundUp) + { + intMant++; + + if (intMant == 1ul << f) + { + biasedExp = 1u; + } + + if (intMant == 1ul << (f + 1)) + { + biasedExp++; + intMant >>= 1; + } + } + + double result; + + if (biasedExp >= (1u << e) - 1u) + { + result = overflowToInf ? SoftFloat64.FPInfinity(sign) : SoftFloat64.FPMaxNormal(sign); + + SoftFloat.FPProcessException(FPException.Overflow, context); + + error = 1d; + } + else + { + result = BitConverter.Int64BitsToDouble( + (long)((sign ? 1ul : 0ul) << 63 | (biasedExp & 0x7FFul) << 52 | (intMant & 0x000FFFFFFFFFFFFFul))); + } + + if (error != 0d) + { + SoftFloat.FPProcessException(FPException.Inexact, context); + } + + return result; + } + + private static double FPConvertNaN(ushort valueBits) + { + return BitConverter.Int64BitsToDouble( + (long)(((ulong)valueBits & 0x8000ul) << 48 | 0x7FF8000000000000ul | ((ulong)valueBits & 0x01FFul) << 42)); + } + } + + static class SoftFloat32_16 + { + public static ushort FPConvert(float value) + { + ExecutionContext context = NativeInterface.GetContext(); + + double real = value.FPUnpackCv(out FPType type, out bool sign, out uint valueBits, context); + + bool altHp = (context.Fpcr & FPCR.Ahp) != 0; + + ushort resultBits; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + if (altHp) + { + resultBits = SoftFloat16.FPZero(sign); + } + else if ((context.Fpcr & FPCR.Dn) != 0) + { + resultBits = SoftFloat16.FPDefaultNaN(); + } + else + { + resultBits = FPConvertNaN(valueBits); + } + + if (type == FPType.SNaN || altHp) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context); + } + } + else if (type == FPType.Infinity) + { + if (altHp) + { + resultBits = (ushort)((sign ? 1u : 0u) << 15 | 0x7FFFu); + + SoftFloat.FPProcessException(FPException.InvalidOp, context); + } + else + { + resultBits = SoftFloat16.FPInfinity(sign); + } + } + else if (type == FPType.Zero) + { + resultBits = SoftFloat16.FPZero(sign); + } + else + { + resultBits = SoftFloat16.FPRoundCv(real, context); + } + + return resultBits; + } + + private static double FPUnpackCv( + this float value, + out FPType type, + out bool sign, + out uint valueBits, + ExecutionContext context) + { + valueBits = (uint)BitConverter.SingleToInt32Bits(value); + + sign = (~valueBits & 0x80000000u) == 0u; + + uint exp32 = (valueBits & 0x7F800000u) >> 23; + uint frac32 = valueBits & 0x007FFFFFu; + + double real; + + if (exp32 == 0u) + { + if (frac32 == 0u || (context.Fpcr & FPCR.Fz) != 0) + { + type = FPType.Zero; + real = 0d; + + if (frac32 != 0u) + { + SoftFloat.FPProcessException(FPException.InputDenorm, context); + } + } + else + { + type = FPType.Nonzero; // Subnormal. + real = Math.Pow(2d, -126) * ((double)frac32 * Math.Pow(2d, -23)); + } + } + else if (exp32 == 0xFFu) + { + if (frac32 == 0u) + { + type = FPType.Infinity; + real = Math.Pow(2d, 1000); + } + else + { + type = (~frac32 & 0x00400000u) == 0u ? FPType.QNaN : FPType.SNaN; + real = 0d; + } + } + else + { + type = FPType.Nonzero; // Normal. + real = Math.Pow(2d, (int)exp32 - 127) * (1d + (double)frac32 * Math.Pow(2d, -23)); + } + + return sign ? -real : real; + } + + private static ushort FPConvertNaN(uint valueBits) + { + return (ushort)((valueBits & 0x80000000u) >> 16 | 0x7E00u | (valueBits & 0x003FE000u) >> 13); + } + } + + static class SoftFloat32 + { + public static float FPAdd(float value1, float value2) + { + return FPAddFpscr(value1, value2, false); + } + + public static float FPAddFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; bool zero2 = type2 == FPType.Zero; + + if (inf1 && inf2 && sign1 == !sign2) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((inf1 && !sign1) || (inf2 && !sign2)) + { + result = FPInfinity(false); + } + else if ((inf1 && sign1) || (inf2 && sign2)) + { + result = FPInfinity(true); + } + else if (zero1 && zero2 && sign1 == sign2) + { + result = FPZero(sign1); + } + else + { + result = value1 + value2; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static int FPCompare(float value1, float value2, bool signalNaNs) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out _, context, fpcr); + + int result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = 0b0011; + + if (type1 == FPType.SNaN || type2 == FPType.SNaN || signalNaNs) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + } + else + { + if (value1 == value2) + { + result = 0b0110; + } + else if (value1 < value2) + { + result = 0b1000; + } + else + { + result = 0b0010; + } + } + + return result; + } + + public static float FPCompareEQ(float value1, float value2) + { + return FPCompareEQFpscr(value1, value2, false); + } + + public static float FPCompareEQFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + float result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = ZerosOrOnes(false); + + if (type1 == FPType.SNaN || type2 == FPType.SNaN) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + } + else + { + result = ZerosOrOnes(value1 == value2); + } + + return result; + } + + public static float FPCompareGE(float value1, float value2) + { + return FPCompareGEFpscr(value1, value2, false); + } + + public static float FPCompareGEFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + float result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = ZerosOrOnes(false); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else + { + result = ZerosOrOnes(value1 >= value2); + } + + return result; + } + + public static float FPCompareGT(float value1, float value2) + { + return FPCompareGTFpscr(value1, value2, false); + } + + public static float FPCompareGTFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + float result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = ZerosOrOnes(false); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else + { + result = ZerosOrOnes(value1 > value2); + } + + return result; + } + + public static float FPCompareLE(float value1, float value2) + { + return FPCompareGE(value2, value1); + } + + public static float FPCompareLT(float value1, float value2) + { + return FPCompareGT(value2, value1); + } + + public static float FPCompareLEFpscr(float value1, float value2, bool standardFpscr) + { + return FPCompareGEFpscr(value2, value1, standardFpscr); + } + + public static float FPCompareLTFpscr(float value1, float value2, bool standardFpscr) + { + return FPCompareGTFpscr(value2, value1, standardFpscr); + } + + public static float FPDiv(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; bool zero2 = type2 == FPType.Zero; + + if ((inf1 && inf2) || (zero1 && zero2)) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if (inf1 || zero2) + { + result = FPInfinity(sign1 ^ sign2); + + if (!inf1) + { + SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr); + } + } + else if (zero1 || inf2) + { + result = FPZero(sign1 ^ sign2); + } + else + { + result = value1 / value2; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPMax(float value1, float value2) + { + return FPMaxFpscr(value1, value2, false); + } + + public static float FPMaxFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + if (value1 > value2) + { + if (type1 == FPType.Infinity) + { + result = FPInfinity(sign1); + } + else if (type1 == FPType.Zero) + { + result = FPZero(sign1 && sign2); + } + else + { + result = value1; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + else + { + if (type2 == FPType.Infinity) + { + result = FPInfinity(sign2); + } + else if (type2 == FPType.Zero) + { + result = FPZero(sign1 && sign2); + } + else + { + result = value2; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + } + + return result; + } + + public static float FPMaxNum(float value1, float value2) + { + return FPMaxNumFpscr(value1, value2, false); + } + + public static float FPMaxNumFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + if (type1 == FPType.QNaN && type2 != FPType.QNaN) + { + value1 = FPInfinity(true); + } + else if (type1 != FPType.QNaN && type2 == FPType.QNaN) + { + value2 = FPInfinity(true); + } + + return FPMaxFpscr(value1, value2, standardFpscr); + } + + public static float FPMin(float value1, float value2) + { + return FPMinFpscr(value1, value2, false); + } + + public static float FPMinFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + if (value1 < value2) + { + if (type1 == FPType.Infinity) + { + result = FPInfinity(sign1); + } + else if (type1 == FPType.Zero) + { + result = FPZero(sign1 || sign2); + } + else + { + result = value1; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + else + { + if (type2 == FPType.Infinity) + { + result = FPInfinity(sign2); + } + else if (type2 == FPType.Zero) + { + result = FPZero(sign1 || sign2); + } + else + { + result = value2; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + } + + return result; + } + + public static float FPMinNum(float value1, float value2) + { + return FPMinNumFpscr(value1, value2, false); + } + + public static float FPMinNumFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + if (type1 == FPType.QNaN && type2 != FPType.QNaN) + { + value1 = FPInfinity(false); + } + else if (type1 != FPType.QNaN && type2 == FPType.QNaN) + { + value2 = FPInfinity(false); + } + + return FPMinFpscr(value1, value2, standardFpscr); + } + + public static float FPMul(float value1, float value2) + { + return FPMulFpscr(value1, value2, false); + } + + public static float FPMulFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else if (zero1 || zero2) + { + result = FPZero(sign1 ^ sign2); + } + else + { + result = value1 * value2; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPMulAdd(float valueA, float value1, float value2) + { + return FPMulAddFpscr(valueA, value1, value2, false); + } + + public static float FPMulAddFpscr(float valueA, float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + valueA = valueA.FPUnpack(out FPType typeA, out bool signA, out uint addend, context, fpcr); + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + bool inf1 = type1 == FPType.Infinity; bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; bool zero2 = type2 == FPType.Zero; + + float result = FPProcessNaNs3(typeA, type1, type2, addend, op1, op2, out bool done, context, fpcr); + + if (typeA == FPType.QNaN && ((inf1 && zero2) || (zero1 && inf2))) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + + if (!done) + { + bool infA = typeA == FPType.Infinity; bool zeroA = typeA == FPType.Zero; + + bool signP = sign1 ^ sign2; + bool infP = inf1 || inf2; + bool zeroP = zero1 || zero2; + + if ((inf1 && zero2) || (zero1 && inf2) || (infA && infP && signA != signP)) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((infA && !signA) || (infP && !signP)) + { + result = FPInfinity(false); + } + else if ((infA && signA) || (infP && signP)) + { + result = FPInfinity(true); + } + else if (zeroA && zeroP && signA == signP) + { + result = FPZero(signA); + } + else + { + result = MathF.FusedMultiplyAdd(value1, value2, valueA); + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPMulSub(float valueA, float value1, float value2) + { + value1 = value1.FPNeg(); + + return FPMulAdd(valueA, value1, value2); + } + + public static float FPMulSubFpscr(float valueA, float value1, float value2, bool standardFpscr) + { + value1 = value1.FPNeg(); + + return FPMulAddFpscr(valueA, value1, value2, standardFpscr); + } + + public static float FPMulX(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPTwo(sign1 ^ sign2); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else if (zero1 || zero2) + { + result = FPZero(sign1 ^ sign2); + } + else + { + result = value1 * value2; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPNegMulAdd(float valueA, float value1, float value2) + { + valueA = valueA.FPNeg(); + value1 = value1.FPNeg(); + + return FPMulAdd(valueA, value1, value2); + } + + public static float FPNegMulSub(float valueA, float value1, float value2) + { + valueA = valueA.FPNeg(); + + return FPMulAdd(valueA, value1, value2); + } + + public static float FPRecipEstimate(float value) + { + return FPRecipEstimateFpscr(value, false); + } + + public static float FPRecipEstimateFpscr(float value, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value.FPUnpack(out FPType type, out bool sign, out uint op, context, fpcr); + + float result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else if (type == FPType.Infinity) + { + result = FPZero(sign); + } + else if (type == FPType.Zero) + { + result = FPInfinity(sign); + + SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr); + } + else if (MathF.Abs(value) < MathF.Pow(2f, -128)) + { + bool overflowToInf; + + switch (fpcr.GetRoundingMode()) + { + default: + case FPRoundingMode.ToNearest: overflowToInf = true; break; + case FPRoundingMode.TowardsPlusInfinity: overflowToInf = !sign; break; + case FPRoundingMode.TowardsMinusInfinity: overflowToInf = sign; break; + case FPRoundingMode.TowardsZero: overflowToInf = false; break; + } + + result = overflowToInf ? FPInfinity(sign) : FPMaxNormal(sign); + + SoftFloat.FPProcessException(FPException.Overflow, context, fpcr); + SoftFloat.FPProcessException(FPException.Inexact, context, fpcr); + } + else if ((fpcr & FPCR.Fz) != 0 && (MathF.Abs(value) >= MathF.Pow(2f, 126))) + { + result = FPZero(sign); + + context.Fpsr |= FPSR.Ufc; + } + else + { + ulong fraction = (ulong)(op & 0x007FFFFFu) << 29; + uint exp = (op & 0x7F800000u) >> 23; + + if (exp == 0u) + { + if ((fraction & 0x0008000000000000ul) == 0ul) + { + fraction = (fraction & 0x0003FFFFFFFFFFFFul) << 2; + exp -= 1u; + } + else + { + fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1; + } + } + + uint scaled = (uint)(((fraction & 0x000FF00000000000ul) | 0x0010000000000000ul) >> 44); + + uint resultExp = 253u - exp; + + uint estimate = (uint)SoftFloat.RecipEstimateTable[scaled - 256u] + 256u; + + fraction = (ulong)(estimate & 0xFFu) << 44; + + if (resultExp == 0u) + { + fraction = ((fraction & 0x000FFFFFFFFFFFFEul) | 0x0010000000000000ul) >> 1; + } + else if (resultExp + 1u == 0u) + { + fraction = ((fraction & 0x000FFFFFFFFFFFFCul) | 0x0010000000000000ul) >> 2; + resultExp = 0u; + } + + result = BitConverter.Int32BitsToSingle( + (int)((sign ? 1u : 0u) << 31 | (resultExp & 0xFFu) << 23 | (uint)(fraction >> 29) & 0x007FFFFFu)); + } + + return result; + } + + public static float FPRecipStep(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.StandardFpcrValue; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; bool zero2 = type2 == FPType.Zero; + + float product; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + product = FPZero(false); + } + else + { + product = FPMulFpscr(value1, value2, true); + } + + result = FPSubFpscr(FPTwo(false), product, true); + } + + return result; + } + + public static float FPRecipStepFused(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPNeg(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPTwo(false); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else + { + result = MathF.FusedMultiplyAdd(value1, value2, 2f); + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPRecpX(float value) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value.FPUnpack(out FPType type, out bool sign, out uint op, context, fpcr); + + float result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else + { + uint notExp = (~op >> 23) & 0xFFu; + uint maxExp = 0xFEu; + + result = BitConverter.Int32BitsToSingle( + (int)((sign ? 1u : 0u) << 31 | (notExp == 0xFFu ? maxExp : notExp) << 23)); + } + + return result; + } + + public static float FPRSqrtEstimate(float value) + { + return FPRSqrtEstimateFpscr(value, false); + } + + public static float FPRSqrtEstimateFpscr(float value, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value.FPUnpack(out FPType type, out bool sign, out uint op, context, fpcr); + + float result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else if (type == FPType.Zero) + { + result = FPInfinity(sign); + + SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr); + } + else if (sign) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if (type == FPType.Infinity) + { + result = FPZero(false); + } + else + { + ulong fraction = (ulong)(op & 0x007FFFFFu) << 29; + uint exp = (op & 0x7F800000u) >> 23; + + if (exp == 0u) + { + while ((fraction & 0x0008000000000000ul) == 0ul) + { + fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1; + exp -= 1u; + } + + fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1; + } + + uint scaled; + + if ((exp & 1u) == 0u) + { + scaled = (uint)(((fraction & 0x000FF00000000000ul) | 0x0010000000000000ul) >> 44); + } + else + { + scaled = (uint)(((fraction & 0x000FE00000000000ul) | 0x0010000000000000ul) >> 45); + } + + uint resultExp = (380u - exp) >> 1; + + uint estimate = (uint)SoftFloat.RecipSqrtEstimateTable[scaled - 128u] + 256u; + + result = BitConverter.Int32BitsToSingle((int)((resultExp & 0xFFu) << 23 | (estimate & 0xFFu) << 15)); + } + + return result; + } + + public static float FPHalvedSub(float value1, float value2, ExecutionContext context, FPCR fpcr) + { + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; bool zero2 = type2 == FPType.Zero; + + if (inf1 && inf2 && sign1 == sign2) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((inf1 && !sign1) || (inf2 && sign2)) + { + result = FPInfinity(false); + } + else if ((inf1 && sign1) || (inf2 && !sign2)) + { + result = FPInfinity(true); + } + else if (zero1 && zero2 && sign1 == !sign2) + { + result = FPZero(sign1); + } + else + { + result = (value1 - value2) / 2.0f; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPRSqrtStep(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.StandardFpcrValue; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; bool zero2 = type2 == FPType.Zero; + + float product; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + product = FPZero(false); + } + else + { + product = FPMulFpscr(value1, value2, true); + } + + result = FPHalvedSub(FPThree(false), product, context, fpcr); + } + + return result; + } + + public static float FPRSqrtStepFused(float value1, float value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPNeg(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPOnePointFive(false); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else + { + result = MathF.FusedMultiplyAdd(value1, value2, 3f) / 2f; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPSqrt(float value) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value = value.FPUnpack(out FPType type, out bool sign, out uint op, context, fpcr); + + float result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else if (type == FPType.Zero) + { + result = FPZero(sign); + } + else if (type == FPType.Infinity && !sign) + { + result = FPInfinity(sign); + } + else if (sign) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else + { + result = MathF.Sqrt(value); + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + + return result; + } + + public static float FPSub(float value1, float value2) + { + return FPSubFpscr(value1, value2, false); + } + + public static float FPSubFpscr(float value1, float value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out uint op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out uint op2, context, fpcr); + + float result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; bool zero2 = type2 == FPType.Zero; + + if (inf1 && inf2 && sign1 == sign2) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((inf1 && !sign1) || (inf2 && sign2)) + { + result = FPInfinity(false); + } + else if ((inf1 && sign1) || (inf2 && !sign2)) + { + result = FPInfinity(true); + } + else if (zero1 && zero2 && sign1 == !sign2) + { + result = FPZero(sign1); + } + else + { + result = value1 - value2; + + if ((fpcr & FPCR.Fz) != 0 && float.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0f); + } + } + } + + return result; + } + + public static float FPDefaultNaN() + { + return BitConverter.Int32BitsToSingle(0x7fc00000); + } + + public static float FPInfinity(bool sign) + { + return sign ? float.NegativeInfinity : float.PositiveInfinity; + } + + public static float FPZero(bool sign) + { + return sign ? -0f : +0f; + } + + public static float FPMaxNormal(bool sign) + { + return sign ? float.MinValue : float.MaxValue; + } + + private static float FPTwo(bool sign) + { + return sign ? -2f : +2f; + } + + private static float FPThree(bool sign) + { + return sign ? -3f : +3f; + } + + private static float FPOnePointFive(bool sign) + { + return sign ? -1.5f : +1.5f; + } + + private static float FPNeg(this float value) + { + return -value; + } + + private static float ZerosOrOnes(bool ones) + { + return BitConverter.Int32BitsToSingle(ones ? -1 : 0); + } + + private static float FPUnpack( + this float value, + out FPType type, + out bool sign, + out uint valueBits, + ExecutionContext context, + FPCR fpcr) + { + valueBits = (uint)BitConverter.SingleToInt32Bits(value); + + sign = (~valueBits & 0x80000000u) == 0u; + + if ((valueBits & 0x7F800000u) == 0u) + { + if ((valueBits & 0x007FFFFFu) == 0u || (fpcr & FPCR.Fz) != 0) + { + type = FPType.Zero; + value = FPZero(sign); + + if ((valueBits & 0x007FFFFFu) != 0u) + { + SoftFloat.FPProcessException(FPException.InputDenorm, context, fpcr); + } + } + else + { + type = FPType.Nonzero; + } + } + else if ((~valueBits & 0x7F800000u) == 0u) + { + if ((valueBits & 0x007FFFFFu) == 0u) + { + type = FPType.Infinity; + } + else + { + type = (~valueBits & 0x00400000u) == 0u ? FPType.QNaN : FPType.SNaN; + value = FPZero(sign); + } + } + else + { + type = FPType.Nonzero; + } + + return value; + } + + private static float FPProcessNaNs( + FPType type1, + FPType type2, + uint op1, + uint op2, + out bool done, + ExecutionContext context, + FPCR fpcr) + { + done = true; + + if (type1 == FPType.SNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.SNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + else if (type1 == FPType.QNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.QNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + + done = false; + + return FPZero(false); + } + + private static float FPProcessNaNs3( + FPType type1, + FPType type2, + FPType type3, + uint op1, + uint op2, + uint op3, + out bool done, + ExecutionContext context, + FPCR fpcr) + { + done = true; + + if (type1 == FPType.SNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.SNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + else if (type3 == FPType.SNaN) + { + return FPProcessNaN(type3, op3, context, fpcr); + } + else if (type1 == FPType.QNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.QNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + else if (type3 == FPType.QNaN) + { + return FPProcessNaN(type3, op3, context, fpcr); + } + + done = false; + + return FPZero(false); + } + + private static float FPProcessNaN(FPType type, uint op, ExecutionContext context, FPCR fpcr) + { + if (type == FPType.SNaN) + { + op |= 1u << 22; + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + + if ((fpcr & FPCR.Dn) != 0) + { + return FPDefaultNaN(); + } + + return BitConverter.Int32BitsToSingle((int)op); + } + } + + static class SoftFloat64_16 + { + public static ushort FPConvert(double value) + { + ExecutionContext context = NativeInterface.GetContext(); + + double real = value.FPUnpackCv(out FPType type, out bool sign, out ulong valueBits, context); + + bool altHp = (context.Fpcr & FPCR.Ahp) != 0; + + ushort resultBits; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + if (altHp) + { + resultBits = SoftFloat16.FPZero(sign); + } + else if ((context.Fpcr & FPCR.Dn) != 0) + { + resultBits = SoftFloat16.FPDefaultNaN(); + } + else + { + resultBits = FPConvertNaN(valueBits); + } + + if (type == FPType.SNaN || altHp) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context); + } + } + else if (type == FPType.Infinity) + { + if (altHp) + { + resultBits = (ushort)((sign ? 1u : 0u) << 15 | 0x7FFFu); + + SoftFloat.FPProcessException(FPException.InvalidOp, context); + } + else + { + resultBits = SoftFloat16.FPInfinity(sign); + } + } + else if (type == FPType.Zero) + { + resultBits = SoftFloat16.FPZero(sign); + } + else + { + resultBits = SoftFloat16.FPRoundCv(real, context); + } + + return resultBits; + } + + private static double FPUnpackCv( + this double value, + out FPType type, + out bool sign, + out ulong valueBits, + ExecutionContext context) + { + valueBits = (ulong)BitConverter.DoubleToInt64Bits(value); + + sign = (~valueBits & 0x8000000000000000ul) == 0u; + + ulong exp64 = (valueBits & 0x7FF0000000000000ul) >> 52; + ulong frac64 = valueBits & 0x000FFFFFFFFFFFFFul; + + double real; + + if (exp64 == 0u) + { + if (frac64 == 0u || (context.Fpcr & FPCR.Fz) != 0) + { + type = FPType.Zero; + real = 0d; + + if (frac64 != 0u) + { + SoftFloat.FPProcessException(FPException.InputDenorm, context); + } + } + else + { + type = FPType.Nonzero; // Subnormal. + real = Math.Pow(2d, -1022) * ((double)frac64 * Math.Pow(2d, -52)); + } + } + else if (exp64 == 0x7FFul) + { + if (frac64 == 0u) + { + type = FPType.Infinity; + real = Math.Pow(2d, 1000000); + } + else + { + type = (~frac64 & 0x0008000000000000ul) == 0u ? FPType.QNaN : FPType.SNaN; + real = 0d; + } + } + else + { + type = FPType.Nonzero; // Normal. + real = Math.Pow(2d, (int)exp64 - 1023) * (1d + (double)frac64 * Math.Pow(2d, -52)); + } + + return sign ? -real : real; + } + + private static ushort FPConvertNaN(ulong valueBits) + { + return (ushort)((valueBits & 0x8000000000000000ul) >> 48 | 0x7E00u | (valueBits & 0x0007FC0000000000ul) >> 42); + } + } + + static class SoftFloat64 + { + public static double FPAdd(double value1, double value2) + { + return FPAddFpscr(value1, value2, false); + } + + public static double FPAddFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; bool zero2 = type2 == FPType.Zero; + + if (inf1 && inf2 && sign1 == !sign2) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((inf1 && !sign1) || (inf2 && !sign2)) + { + result = FPInfinity(false); + } + else if ((inf1 && sign1) || (inf2 && sign2)) + { + result = FPInfinity(true); + } + else if (zero1 && zero2 && sign1 == sign2) + { + result = FPZero(sign1); + } + else + { + result = value1 + value2; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static int FPCompare(double value1, double value2, bool signalNaNs) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out _, context, fpcr); + + int result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = 0b0011; + + if (type1 == FPType.SNaN || type2 == FPType.SNaN || signalNaNs) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + } + else + { + if (value1 == value2) + { + result = 0b0110; + } + else if (value1 < value2) + { + result = 0b1000; + } + else + { + result = 0b0010; + } + } + + return result; + } + + public static double FPCompareEQ(double value1, double value2) + { + return FPCompareEQFpscr(value1, value2, false); + } + + public static double FPCompareEQFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + double result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = ZerosOrOnes(false); + + if (type1 == FPType.SNaN || type2 == FPType.SNaN) + { + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + } + else + { + result = ZerosOrOnes(value1 == value2); + } + + return result; + } + + public static double FPCompareGE(double value1, double value2) + { + return FPCompareGEFpscr(value1, value2, false); + } + + public static double FPCompareGEFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + double result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = ZerosOrOnes(false); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else + { + result = ZerosOrOnes(value1 >= value2); + } + + return result; + } + + public static double FPCompareGT(double value1, double value2) + { + return FPCompareGTFpscr(value1, value2, false); + } + + public static double FPCompareGTFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + double result; + + if (type1 == FPType.SNaN || type1 == FPType.QNaN || type2 == FPType.SNaN || type2 == FPType.QNaN) + { + result = ZerosOrOnes(false); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else + { + result = ZerosOrOnes(value1 > value2); + } + + return result; + } + + public static double FPCompareLE(double value1, double value2) + { + return FPCompareGE(value2, value1); + } + + public static double FPCompareLT(double value1, double value2) + { + return FPCompareGT(value2, value1); + } + + public static double FPCompareLEFpscr(double value1, double value2, bool standardFpscr) + { + return FPCompareGEFpscr(value2, value1, standardFpscr); + } + + public static double FPCompareLTFpscr(double value1, double value2, bool standardFpscr) + { + return FPCompareGTFpscr(value2, value1, standardFpscr); + } + + public static double FPDiv(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; bool zero2 = type2 == FPType.Zero; + + if ((inf1 && inf2) || (zero1 && zero2)) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if (inf1 || zero2) + { + result = FPInfinity(sign1 ^ sign2); + + if (!inf1) + { + SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr); + } + } + else if (zero1 || inf2) + { + result = FPZero(sign1 ^ sign2); + } + else + { + result = value1 / value2; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPMax(double value1, double value2) + { + return FPMaxFpscr(value1, value2, false); + } + + public static double FPMaxFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + if (value1 > value2) + { + if (type1 == FPType.Infinity) + { + result = FPInfinity(sign1); + } + else if (type1 == FPType.Zero) + { + result = FPZero(sign1 && sign2); + } + else + { + result = value1; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + else + { + if (type2 == FPType.Infinity) + { + result = FPInfinity(sign2); + } + else if (type2 == FPType.Zero) + { + result = FPZero(sign1 && sign2); + } + else + { + result = value2; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + } + + return result; + } + + public static double FPMaxNum(double value1, double value2) + { + return FPMaxNumFpscr(value1, value2, false); + } + + public static double FPMaxNumFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + if (type1 == FPType.QNaN && type2 != FPType.QNaN) + { + value1 = FPInfinity(true); + } + else if (type1 != FPType.QNaN && type2 == FPType.QNaN) + { + value2 = FPInfinity(true); + } + + return FPMaxFpscr(value1, value2, standardFpscr); + } + + public static double FPMin(double value1, double value2) + { + return FPMinFpscr(value1, value2, false); + } + + public static double FPMinFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + if (value1 < value2) + { + if (type1 == FPType.Infinity) + { + result = FPInfinity(sign1); + } + else if (type1 == FPType.Zero) + { + result = FPZero(sign1 || sign2); + } + else + { + result = value1; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + else + { + if (type2 == FPType.Infinity) + { + result = FPInfinity(sign2); + } + else if (type2 == FPType.Zero) + { + result = FPZero(sign1 || sign2); + } + else + { + result = value2; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + } + + return result; + } + + public static double FPMinNum(double value1, double value2) + { + return FPMinNumFpscr(value1, value2, false); + } + + public static double FPMinNumFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1.FPUnpack(out FPType type1, out _, out _, context, fpcr); + value2.FPUnpack(out FPType type2, out _, out _, context, fpcr); + + if (type1 == FPType.QNaN && type2 != FPType.QNaN) + { + value1 = FPInfinity(false); + } + else if (type1 != FPType.QNaN && type2 == FPType.QNaN) + { + value2 = FPInfinity(false); + } + + return FPMinFpscr(value1, value2, standardFpscr); + } + + public static double FPMul(double value1, double value2) + { + return FPMulFpscr(value1, value2, false); + } + + public static double FPMulFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else if (zero1 || zero2) + { + result = FPZero(sign1 ^ sign2); + } + else + { + result = value1 * value2; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPMulAdd(double valueA, double value1, double value2) + { + return FPMulAddFpscr(valueA, value1, value2, false); + } + + public static double FPMulAddFpscr(double valueA, double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + valueA = valueA.FPUnpack(out FPType typeA, out bool signA, out ulong addend, context, fpcr); + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + bool inf1 = type1 == FPType.Infinity; bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; bool zero2 = type2 == FPType.Zero; + + double result = FPProcessNaNs3(typeA, type1, type2, addend, op1, op2, out bool done, context, fpcr); + + if (typeA == FPType.QNaN && ((inf1 && zero2) || (zero1 && inf2))) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + + if (!done) + { + bool infA = typeA == FPType.Infinity; bool zeroA = typeA == FPType.Zero; + + bool signP = sign1 ^ sign2; + bool infP = inf1 || inf2; + bool zeroP = zero1 || zero2; + + if ((inf1 && zero2) || (zero1 && inf2) || (infA && infP && signA != signP)) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((infA && !signA) || (infP && !signP)) + { + result = FPInfinity(false); + } + else if ((infA && signA) || (infP && signP)) + { + result = FPInfinity(true); + } + else if (zeroA && zeroP && signA == signP) + { + result = FPZero(signA); + } + else + { + result = Math.FusedMultiplyAdd(value1, value2, valueA); + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPMulSub(double valueA, double value1, double value2) + { + value1 = value1.FPNeg(); + + return FPMulAdd(valueA, value1, value2); + } + + public static double FPMulSubFpscr(double valueA, double value1, double value2, bool standardFpscr) + { + value1 = value1.FPNeg(); + + return FPMulAddFpscr(valueA, value1, value2, standardFpscr); + } + + public static double FPMulX(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPTwo(sign1 ^ sign2); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else if (zero1 || zero2) + { + result = FPZero(sign1 ^ sign2); + } + else + { + result = value1 * value2; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPNegMulAdd(double valueA, double value1, double value2) + { + valueA = valueA.FPNeg(); + value1 = value1.FPNeg(); + + return FPMulAdd(valueA, value1, value2); + } + + public static double FPNegMulSub(double valueA, double value1, double value2) + { + valueA = valueA.FPNeg(); + + return FPMulAdd(valueA, value1, value2); + } + + public static double FPRecipEstimate(double value) + { + return FPRecipEstimateFpscr(value, false); + } + + public static double FPRecipEstimateFpscr(double value, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value.FPUnpack(out FPType type, out bool sign, out ulong op, context, fpcr); + + double result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else if (type == FPType.Infinity) + { + result = FPZero(sign); + } + else if (type == FPType.Zero) + { + result = FPInfinity(sign); + + SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr); + } + else if (Math.Abs(value) < Math.Pow(2d, -1024)) + { + bool overflowToInf; + + switch (fpcr.GetRoundingMode()) + { + default: + case FPRoundingMode.ToNearest: overflowToInf = true; break; + case FPRoundingMode.TowardsPlusInfinity: overflowToInf = !sign; break; + case FPRoundingMode.TowardsMinusInfinity: overflowToInf = sign; break; + case FPRoundingMode.TowardsZero: overflowToInf = false; break; + } + + result = overflowToInf ? FPInfinity(sign) : FPMaxNormal(sign); + + SoftFloat.FPProcessException(FPException.Overflow, context, fpcr); + SoftFloat.FPProcessException(FPException.Inexact, context, fpcr); + } + else if ((fpcr & FPCR.Fz) != 0 && (Math.Abs(value) >= Math.Pow(2d, 1022))) + { + result = FPZero(sign); + + context.Fpsr |= FPSR.Ufc; + } + else + { + ulong fraction = op & 0x000FFFFFFFFFFFFFul; + uint exp = (uint)((op & 0x7FF0000000000000ul) >> 52); + + if (exp == 0u) + { + if ((fraction & 0x0008000000000000ul) == 0ul) + { + fraction = (fraction & 0x0003FFFFFFFFFFFFul) << 2; + exp -= 1u; + } + else + { + fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1; + } + } + + uint scaled = (uint)(((fraction & 0x000FF00000000000ul) | 0x0010000000000000ul) >> 44); + + uint resultExp = 2045u - exp; + + uint estimate = (uint)SoftFloat.RecipEstimateTable[scaled - 256u] + 256u; + + fraction = (ulong)(estimate & 0xFFu) << 44; + + if (resultExp == 0u) + { + fraction = ((fraction & 0x000FFFFFFFFFFFFEul) | 0x0010000000000000ul) >> 1; + } + else if (resultExp + 1u == 0u) + { + fraction = ((fraction & 0x000FFFFFFFFFFFFCul) | 0x0010000000000000ul) >> 2; + resultExp = 0u; + } + + result = BitConverter.Int64BitsToDouble( + (long)((sign ? 1ul : 0ul) << 63 | (resultExp & 0x7FFul) << 52 | (fraction & 0x000FFFFFFFFFFFFFul))); + } + + return result; + } + + public static double FPRecipStep(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.StandardFpcrValue; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; bool zero2 = type2 == FPType.Zero; + + double product; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + product = FPZero(false); + } + else + { + product = FPMulFpscr(value1, value2, true); + } + + result = FPSubFpscr(FPTwo(false), product, true); + } + + return result; + } + + public static double FPRecipStepFused(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPNeg(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPTwo(false); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else + { + result = Math.FusedMultiplyAdd(value1, value2, 2d); + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPRecpX(double value) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value.FPUnpack(out FPType type, out bool sign, out ulong op, context, fpcr); + + double result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else + { + ulong notExp = (~op >> 52) & 0x7FFul; + ulong maxExp = 0x7FEul; + + result = BitConverter.Int64BitsToDouble( + (long)((sign ? 1ul : 0ul) << 63 | (notExp == 0x7FFul ? maxExp : notExp) << 52)); + } + + return result; + } + + public static double FPRSqrtEstimate(double value) + { + return FPRSqrtEstimateFpscr(value, false); + } + + public static double FPRSqrtEstimateFpscr(double value, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value.FPUnpack(out FPType type, out bool sign, out ulong op, context, fpcr); + + double result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else if (type == FPType.Zero) + { + result = FPInfinity(sign); + + SoftFloat.FPProcessException(FPException.DivideByZero, context, fpcr); + } + else if (sign) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if (type == FPType.Infinity) + { + result = FPZero(false); + } + else + { + ulong fraction = op & 0x000FFFFFFFFFFFFFul; + uint exp = (uint)((op & 0x7FF0000000000000ul) >> 52); + + if (exp == 0u) + { + while ((fraction & 0x0008000000000000ul) == 0ul) + { + fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1; + exp -= 1u; + } + + fraction = (fraction & 0x0007FFFFFFFFFFFFul) << 1; + } + + uint scaled; + + if ((exp & 1u) == 0u) + { + scaled = (uint)(((fraction & 0x000FF00000000000ul) | 0x0010000000000000ul) >> 44); + } + else + { + scaled = (uint)(((fraction & 0x000FE00000000000ul) | 0x0010000000000000ul) >> 45); + } + + uint resultExp = (3068u - exp) >> 1; + + uint estimate = (uint)SoftFloat.RecipSqrtEstimateTable[scaled - 128u] + 256u; + + result = BitConverter.Int64BitsToDouble((long)((resultExp & 0x7FFul) << 52 | (estimate & 0xFFul) << 44)); + } + + return result; + } + + public static double FPHalvedSub(double value1, double value2, ExecutionContext context, FPCR fpcr) + { + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; bool zero2 = type2 == FPType.Zero; + + if (inf1 && inf2 && sign1 == sign2) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((inf1 && !sign1) || (inf2 && sign2)) + { + result = FPInfinity(false); + } + else if ((inf1 && sign1) || (inf2 && !sign2)) + { + result = FPInfinity(true); + } + else if (zero1 && zero2 && sign1 == !sign2) + { + result = FPZero(sign1); + } + else + { + result = (value1 - value2) / 2.0; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPRSqrtStep(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.StandardFpcrValue; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; bool zero2 = type2 == FPType.Zero; + + double product; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + product = FPZero(false); + } + else + { + product = FPMulFpscr(value1, value2, true); + } + + result = FPHalvedSub(FPThree(false), product, context, fpcr); + } + + return result; + } + + public static double FPRSqrtStepFused(double value1, double value2) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value1 = value1.FPNeg(); + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; bool zero2 = type2 == FPType.Zero; + + if ((inf1 && zero2) || (zero1 && inf2)) + { + result = FPOnePointFive(false); + } + else if (inf1 || inf2) + { + result = FPInfinity(sign1 ^ sign2); + } + else + { + result = Math.FusedMultiplyAdd(value1, value2, 3d) / 2d; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPSqrt(double value) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = context.Fpcr; + + value = value.FPUnpack(out FPType type, out bool sign, out ulong op, context, fpcr); + + double result; + + if (type == FPType.SNaN || type == FPType.QNaN) + { + result = FPProcessNaN(type, op, context, fpcr); + } + else if (type == FPType.Zero) + { + result = FPZero(sign); + } + else if (type == FPType.Infinity && !sign) + { + result = FPInfinity(sign); + } + else if (sign) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else + { + result = Math.Sqrt(value); + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + + return result; + } + + public static double FPSub(double value1, double value2) + { + return FPSubFpscr(value1, value2, false); + } + + public static double FPSubFpscr(double value1, double value2, bool standardFpscr) + { + ExecutionContext context = NativeInterface.GetContext(); + FPCR fpcr = standardFpscr ? context.StandardFpcrValue : context.Fpcr; + + value1 = value1.FPUnpack(out FPType type1, out bool sign1, out ulong op1, context, fpcr); + value2 = value2.FPUnpack(out FPType type2, out bool sign2, out ulong op2, context, fpcr); + + double result = FPProcessNaNs(type1, type2, op1, op2, out bool done, context, fpcr); + + if (!done) + { + bool inf1 = type1 == FPType.Infinity; bool zero1 = type1 == FPType.Zero; + bool inf2 = type2 == FPType.Infinity; bool zero2 = type2 == FPType.Zero; + + if (inf1 && inf2 && sign1 == sign2) + { + result = FPDefaultNaN(); + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + else if ((inf1 && !sign1) || (inf2 && sign2)) + { + result = FPInfinity(false); + } + else if ((inf1 && sign1) || (inf2 && !sign2)) + { + result = FPInfinity(true); + } + else if (zero1 && zero2 && sign1 == !sign2) + { + result = FPZero(sign1); + } + else + { + result = value1 - value2; + + if ((fpcr & FPCR.Fz) != 0 && double.IsSubnormal(result)) + { + context.Fpsr |= FPSR.Ufc; + + result = FPZero(result < 0d); + } + } + } + + return result; + } + + public static double FPDefaultNaN() + { + return BitConverter.Int64BitsToDouble(0x7ff8000000000000); + } + + public static double FPInfinity(bool sign) + { + return sign ? double.NegativeInfinity : double.PositiveInfinity; + } + + public static double FPZero(bool sign) + { + return sign ? -0d : +0d; + } + + public static double FPMaxNormal(bool sign) + { + return sign ? double.MinValue : double.MaxValue; + } + + private static double FPTwo(bool sign) + { + return sign ? -2d : +2d; + } + + private static double FPThree(bool sign) + { + return sign ? -3d : +3d; + } + + private static double FPOnePointFive(bool sign) + { + return sign ? -1.5d : +1.5d; + } + + private static double FPNeg(this double value) + { + return -value; + } + + private static double ZerosOrOnes(bool ones) + { + return BitConverter.Int64BitsToDouble(ones ? -1L : 0L); + } + + private static double FPUnpack( + this double value, + out FPType type, + out bool sign, + out ulong valueBits, + ExecutionContext context, + FPCR fpcr) + { + valueBits = (ulong)BitConverter.DoubleToInt64Bits(value); + + sign = (~valueBits & 0x8000000000000000ul) == 0ul; + + if ((valueBits & 0x7FF0000000000000ul) == 0ul) + { + if ((valueBits & 0x000FFFFFFFFFFFFFul) == 0ul || (fpcr & FPCR.Fz) != 0) + { + type = FPType.Zero; + value = FPZero(sign); + + if ((valueBits & 0x000FFFFFFFFFFFFFul) != 0ul) + { + SoftFloat.FPProcessException(FPException.InputDenorm, context, fpcr); + } + } + else + { + type = FPType.Nonzero; + } + } + else if ((~valueBits & 0x7FF0000000000000ul) == 0ul) + { + if ((valueBits & 0x000FFFFFFFFFFFFFul) == 0ul) + { + type = FPType.Infinity; + } + else + { + type = (~valueBits & 0x0008000000000000ul) == 0ul ? FPType.QNaN : FPType.SNaN; + value = FPZero(sign); + } + } + else + { + type = FPType.Nonzero; + } + + return value; + } + + private static double FPProcessNaNs( + FPType type1, + FPType type2, + ulong op1, + ulong op2, + out bool done, + ExecutionContext context, + FPCR fpcr) + { + done = true; + + if (type1 == FPType.SNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.SNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + else if (type1 == FPType.QNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.QNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + + done = false; + + return FPZero(false); + } + + private static double FPProcessNaNs3( + FPType type1, + FPType type2, + FPType type3, + ulong op1, + ulong op2, + ulong op3, + out bool done, + ExecutionContext context, + FPCR fpcr) + { + done = true; + + if (type1 == FPType.SNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.SNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + else if (type3 == FPType.SNaN) + { + return FPProcessNaN(type3, op3, context, fpcr); + } + else if (type1 == FPType.QNaN) + { + return FPProcessNaN(type1, op1, context, fpcr); + } + else if (type2 == FPType.QNaN) + { + return FPProcessNaN(type2, op2, context, fpcr); + } + else if (type3 == FPType.QNaN) + { + return FPProcessNaN(type3, op3, context, fpcr); + } + + done = false; + + return FPZero(false); + } + + private static double FPProcessNaN(FPType type, ulong op, ExecutionContext context, FPCR fpcr) + { + if (type == FPType.SNaN) + { + op |= 1ul << 51; + + SoftFloat.FPProcessException(FPException.InvalidOp, context, fpcr); + } + + if ((fpcr & FPCR.Dn) != 0) + { + return FPDefaultNaN(); + } + + return BitConverter.Int64BitsToDouble((long)op); + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/BasicBlock.cs b/src/ARMeilleure/IntermediateRepresentation/BasicBlock.cs new file mode 100644 index 00000000..07bd8b67 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/BasicBlock.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.IntermediateRepresentation +{ + class BasicBlock : IEquatable, IIntrusiveListNode + { + private const uint MaxSuccessors = 2; + + private int _succCount; + private BasicBlock _succ0; + private BasicBlock _succ1; + private HashSet _domFrontiers; + + public int Index { get; set; } + public BasicBlockFrequency Frequency { get; set; } + public BasicBlock ListPrevious { get; set; } + public BasicBlock ListNext { get; set; } + public IntrusiveList Operations { get; } + public List Predecessors { get; } + public BasicBlock ImmediateDominator { get; set; } + + public int SuccessorsCount => _succCount; + + public HashSet DominanceFrontiers + { + get + { + if (_domFrontiers == null) + { + _domFrontiers = new HashSet(); + } + + return _domFrontiers; + } + } + + public BasicBlock() : this(index: -1) { } + + public BasicBlock(int index) + { + Operations = new IntrusiveList(); + Predecessors = new List(); + + Index = index; + } + + public void AddSuccessor(BasicBlock block) + { + ArgumentNullException.ThrowIfNull(block); + + if ((uint)_succCount + 1 > MaxSuccessors) + { + ThrowSuccessorOverflow(); + } + + block.Predecessors.Add(this); + + GetSuccessorUnsafe(_succCount++) = block; + } + + public void RemoveSuccessor(int index) + { + if ((uint)index >= (uint)_succCount) + { + ThrowOutOfRange(nameof(index)); + } + + ref BasicBlock oldBlock = ref GetSuccessorUnsafe(index); + + oldBlock.Predecessors.Remove(this); + oldBlock = null; + + if (index == 0) + { + _succ0 = _succ1; + } + + _succCount--; + } + + public BasicBlock GetSuccessor(int index) + { + if ((uint)index >= (uint)_succCount) + { + ThrowOutOfRange(nameof(index)); + } + + return GetSuccessorUnsafe(index); + } + + private ref BasicBlock GetSuccessorUnsafe(int index) + { + return ref Unsafe.Add(ref _succ0, index); + } + + public void SetSuccessor(int index, BasicBlock block) + { + ArgumentNullException.ThrowIfNull(block); + + if ((uint)index >= (uint)_succCount) + { + ThrowOutOfRange(nameof(index)); + } + + ref BasicBlock oldBlock = ref GetSuccessorUnsafe(index); + + oldBlock.Predecessors.Remove(this); + block.Predecessors.Add(this); + + oldBlock = block; + } + + public void Append(Operation node) + { + Operation last = Operations.Last; + + // Append node before terminal or to end if no terminal. + if (last == default) + { + Operations.AddLast(node); + + return; + } + + switch (last.Instruction) + { + case Instruction.Return: + case Instruction.Tailcall: + case Instruction.BranchIf: + Operations.AddBefore(last, node); + break; + + default: + Operations.AddLast(node); + break; + } + } + + private static void ThrowOutOfRange(string name) => throw new ArgumentOutOfRangeException(name); + private static void ThrowSuccessorOverflow() => throw new OverflowException($"BasicBlock can only have {MaxSuccessors} successors."); + + public bool Equals(BasicBlock other) + { + return other == this; + } + + public override bool Equals(object obj) + { + return Equals(obj as BasicBlock); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/IntermediateRepresentation/BasicBlockFrequency.cs b/src/ARMeilleure/IntermediateRepresentation/BasicBlockFrequency.cs new file mode 100644 index 00000000..96cfee35 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/BasicBlockFrequency.cs @@ -0,0 +1,8 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum BasicBlockFrequency + { + Default, + Cold + } +} \ No newline at end of file diff --git a/src/ARMeilleure/IntermediateRepresentation/Comparison.cs b/src/ARMeilleure/IntermediateRepresentation/Comparison.cs new file mode 100644 index 00000000..628ce105 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/Comparison.cs @@ -0,0 +1,24 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum Comparison + { + Equal = 0, + NotEqual = 1, + Greater = 2, + LessOrEqual = 3, + GreaterUI = 4, + LessOrEqualUI = 5, + GreaterOrEqual = 6, + Less = 7, + GreaterOrEqualUI = 8, + LessUI = 9 + } + + static class ComparisonExtensions + { + public static Comparison Invert(this Comparison comp) + { + return (Comparison)((int)comp ^ 1); + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/IIntrusiveListNode.cs b/src/ARMeilleure/IntermediateRepresentation/IIntrusiveListNode.cs new file mode 100644 index 00000000..caa9b83f --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/IIntrusiveListNode.cs @@ -0,0 +1,8 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + interface IIntrusiveListNode + { + T ListPrevious { get; set; } + T ListNext { get; set; } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/Instruction.cs b/src/ARMeilleure/IntermediateRepresentation/Instruction.cs new file mode 100644 index 00000000..b55fe1da --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/Instruction.cs @@ -0,0 +1,72 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum Instruction : ushort + { + Add, + BitwiseAnd, + BitwiseExclusiveOr, + BitwiseNot, + BitwiseOr, + BranchIf, + ByteSwap, + Call, + Compare, + CompareAndSwap, + CompareAndSwap16, + CompareAndSwap8, + ConditionalSelect, + ConvertI64ToI32, + ConvertToFP, + ConvertToFPUI, + Copy, + CountLeadingZeros, + Divide, + DivideUI, + Load, + Load16, + Load8, + LoadArgument, + MemoryBarrier, + Multiply, + Multiply64HighSI, + Multiply64HighUI, + Negate, + Return, + RotateRight, + ShiftLeft, + ShiftRightSI, + ShiftRightUI, + SignExtend16, + SignExtend32, + SignExtend8, + StackAlloc, + Store, + Store16, + Store8, + Subtract, + Tailcall, + VectorCreateScalar, + VectorExtract, + VectorExtract16, + VectorExtract8, + VectorInsert, + VectorInsert16, + VectorInsert8, + VectorOne, + VectorZero, + VectorZeroUpper64, + VectorZeroUpper96, + ZeroExtend16, + ZeroExtend32, + ZeroExtend8, + + Clobber, + Extended, + Fill, + LoadFromContext, + Phi, + Spill, + SpillArg, + StoreToContext + } +} \ No newline at end of file diff --git a/src/ARMeilleure/IntermediateRepresentation/Intrinsic.cs b/src/ARMeilleure/IntermediateRepresentation/Intrinsic.cs new file mode 100644 index 00000000..f5a776fa --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/Intrinsic.cs @@ -0,0 +1,636 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum Intrinsic : ushort + { + // X86 (SSE and AVX) + + X86Addpd, + X86Addps, + X86Addsd, + X86Addss, + X86Aesdec, + X86Aesdeclast, + X86Aesenc, + X86Aesenclast, + X86Aesimc, + X86Andnpd, + X86Andnps, + X86Andpd, + X86Andps, + X86Blendvpd, + X86Blendvps, + X86Cmppd, + X86Cmpps, + X86Cmpsd, + X86Cmpss, + X86Comisdeq, + X86Comisdge, + X86Comisdlt, + X86Comisseq, + X86Comissge, + X86Comisslt, + X86Crc32, + X86Crc32_16, + X86Crc32_8, + X86Cvtdq2pd, + X86Cvtdq2ps, + X86Cvtpd2dq, + X86Cvtpd2ps, + X86Cvtps2dq, + X86Cvtps2pd, + X86Cvtsd2si, + X86Cvtsd2ss, + X86Cvtsi2sd, + X86Cvtsi2si, + X86Cvtsi2ss, + X86Cvtss2sd, + X86Cvtss2si, + X86Divpd, + X86Divps, + X86Divsd, + X86Divss, + X86Gf2p8affineqb, + X86Haddpd, + X86Haddps, + X86Insertps, + X86Ldmxcsr, + X86Maxpd, + X86Maxps, + X86Maxsd, + X86Maxss, + X86Minpd, + X86Minps, + X86Minsd, + X86Minss, + X86Movhlps, + X86Movlhps, + X86Movss, + X86Mulpd, + X86Mulps, + X86Mulsd, + X86Mulss, + X86Paddb, + X86Paddd, + X86Paddq, + X86Paddw, + X86Palignr, + X86Pand, + X86Pandn, + X86Pavgb, + X86Pavgw, + X86Pblendvb, + X86Pclmulqdq, + X86Pcmpeqb, + X86Pcmpeqd, + X86Pcmpeqq, + X86Pcmpeqw, + X86Pcmpgtb, + X86Pcmpgtd, + X86Pcmpgtq, + X86Pcmpgtw, + X86Pmaxsb, + X86Pmaxsd, + X86Pmaxsw, + X86Pmaxub, + X86Pmaxud, + X86Pmaxuw, + X86Pminsb, + X86Pminsd, + X86Pminsw, + X86Pminub, + X86Pminud, + X86Pminuw, + X86Pmovsxbw, + X86Pmovsxdq, + X86Pmovsxwd, + X86Pmovzxbw, + X86Pmovzxdq, + X86Pmovzxwd, + X86Pmulld, + X86Pmullw, + X86Popcnt, + X86Por, + X86Pshufb, + X86Pshufd, + X86Pslld, + X86Pslldq, + X86Psllq, + X86Psllw, + X86Psrad, + X86Psraw, + X86Psrld, + X86Psrlq, + X86Psrldq, + X86Psrlw, + X86Psubb, + X86Psubd, + X86Psubq, + X86Psubw, + X86Punpckhbw, + X86Punpckhdq, + X86Punpckhqdq, + X86Punpckhwd, + X86Punpcklbw, + X86Punpckldq, + X86Punpcklqdq, + X86Punpcklwd, + X86Pxor, + X86Rcpps, + X86Rcpss, + X86Roundpd, + X86Roundps, + X86Roundsd, + X86Roundss, + X86Rsqrtps, + X86Rsqrtss, + X86Sha256Msg1, + X86Sha256Msg2, + X86Sha256Rnds2, + X86Shufpd, + X86Shufps, + X86Sqrtpd, + X86Sqrtps, + X86Sqrtsd, + X86Sqrtss, + X86Stmxcsr, + X86Subpd, + X86Subps, + X86Subsd, + X86Subss, + X86Unpckhpd, + X86Unpckhps, + X86Unpcklpd, + X86Unpcklps, + X86Vcvtph2ps, + X86Vcvtps2ph, + X86Vfmadd231pd, + X86Vfmadd231ps, + X86Vfmadd231sd, + X86Vfmadd231ss, + X86Vfmsub231sd, + X86Vfmsub231ss, + X86Vfnmadd231pd, + X86Vfnmadd231ps, + X86Vfnmadd231sd, + X86Vfnmadd231ss, + X86Vfnmsub231sd, + X86Vfnmsub231ss, + X86Vpternlogd, + X86Xorpd, + X86Xorps, + + // Arm64 (FP and Advanced SIMD) + + Arm64AbsS, + Arm64AbsV, + Arm64AddhnV, + Arm64AddpS, + Arm64AddpV, + Arm64AddvV, + Arm64AddS, + Arm64AddV, + Arm64AesdV, + Arm64AeseV, + Arm64AesimcV, + Arm64AesmcV, + Arm64AndV, + Arm64BicVi, + Arm64BicV, + Arm64BifV, + Arm64BitV, + Arm64BslV, + Arm64ClsV, + Arm64ClzV, + Arm64CmeqS, + Arm64CmeqV, + Arm64CmeqSz, + Arm64CmeqVz, + Arm64CmgeS, + Arm64CmgeV, + Arm64CmgeSz, + Arm64CmgeVz, + Arm64CmgtS, + Arm64CmgtV, + Arm64CmgtSz, + Arm64CmgtVz, + Arm64CmhiS, + Arm64CmhiV, + Arm64CmhsS, + Arm64CmhsV, + Arm64CmleSz, + Arm64CmleVz, + Arm64CmltSz, + Arm64CmltVz, + Arm64CmtstS, + Arm64CmtstV, + Arm64CntV, + Arm64DupSe, + Arm64DupVe, + Arm64DupGp, + Arm64EorV, + Arm64ExtV, + Arm64FabdS, + Arm64FabdV, + Arm64FabsV, + Arm64FabsS, + Arm64FacgeS, + Arm64FacgeV, + Arm64FacgtS, + Arm64FacgtV, + Arm64FaddpS, + Arm64FaddpV, + Arm64FaddV, + Arm64FaddS, + Arm64FccmpeS, + Arm64FccmpS, + Arm64FcmeqS, + Arm64FcmeqV, + Arm64FcmeqSz, + Arm64FcmeqVz, + Arm64FcmgeS, + Arm64FcmgeV, + Arm64FcmgeSz, + Arm64FcmgeVz, + Arm64FcmgtS, + Arm64FcmgtV, + Arm64FcmgtSz, + Arm64FcmgtVz, + Arm64FcmleSz, + Arm64FcmleVz, + Arm64FcmltSz, + Arm64FcmltVz, + Arm64FcmpeS, + Arm64FcmpS, + Arm64FcselS, + Arm64FcvtasS, + Arm64FcvtasV, + Arm64FcvtasGp, + Arm64FcvtauS, + Arm64FcvtauV, + Arm64FcvtauGp, + Arm64FcvtlV, + Arm64FcvtmsS, + Arm64FcvtmsV, + Arm64FcvtmsGp, + Arm64FcvtmuS, + Arm64FcvtmuV, + Arm64FcvtmuGp, + Arm64FcvtnsS, + Arm64FcvtnsV, + Arm64FcvtnsGp, + Arm64FcvtnuS, + Arm64FcvtnuV, + Arm64FcvtnuGp, + Arm64FcvtnV, + Arm64FcvtpsS, + Arm64FcvtpsV, + Arm64FcvtpsGp, + Arm64FcvtpuS, + Arm64FcvtpuV, + Arm64FcvtpuGp, + Arm64FcvtxnS, + Arm64FcvtxnV, + Arm64FcvtzsSFixed, + Arm64FcvtzsVFixed, + Arm64FcvtzsS, + Arm64FcvtzsV, + Arm64FcvtzsGpFixed, + Arm64FcvtzsGp, + Arm64FcvtzuSFixed, + Arm64FcvtzuVFixed, + Arm64FcvtzuS, + Arm64FcvtzuV, + Arm64FcvtzuGpFixed, + Arm64FcvtzuGp, + Arm64FcvtS, + Arm64FdivV, + Arm64FdivS, + Arm64FmaddS, + Arm64FmaxnmpS, + Arm64FmaxnmpV, + Arm64FmaxnmvV, + Arm64FmaxnmV, + Arm64FmaxnmS, + Arm64FmaxpS, + Arm64FmaxpV, + Arm64FmaxvV, + Arm64FmaxV, + Arm64FmaxS, + Arm64FminnmpS, + Arm64FminnmpV, + Arm64FminnmvV, + Arm64FminnmV, + Arm64FminnmS, + Arm64FminpS, + Arm64FminpV, + Arm64FminvV, + Arm64FminV, + Arm64FminS, + Arm64FmlaSe, + Arm64FmlaVe, + Arm64FmlaV, + Arm64FmlsSe, + Arm64FmlsVe, + Arm64FmlsV, + Arm64FmovVi, + Arm64FmovS, + Arm64FmovGp, + Arm64FmovSi, + Arm64FmsubS, + Arm64FmulxSe, + Arm64FmulxVe, + Arm64FmulxS, + Arm64FmulxV, + Arm64FmulSe, + Arm64FmulVe, + Arm64FmulV, + Arm64FmulS, + Arm64FnegV, + Arm64FnegS, + Arm64FnmaddS, + Arm64FnmsubS, + Arm64FnmulS, + Arm64FrecpeS, + Arm64FrecpeV, + Arm64FrecpsS, + Arm64FrecpsV, + Arm64FrecpxS, + Arm64FrintaV, + Arm64FrintaS, + Arm64FrintiV, + Arm64FrintiS, + Arm64FrintmV, + Arm64FrintmS, + Arm64FrintnV, + Arm64FrintnS, + Arm64FrintpV, + Arm64FrintpS, + Arm64FrintxV, + Arm64FrintxS, + Arm64FrintzV, + Arm64FrintzS, + Arm64FrsqrteS, + Arm64FrsqrteV, + Arm64FrsqrtsS, + Arm64FrsqrtsV, + Arm64FsqrtV, + Arm64FsqrtS, + Arm64FsubV, + Arm64FsubS, + Arm64InsVe, + Arm64InsGp, + Arm64Ld1rV, + Arm64Ld1Vms, + Arm64Ld1Vss, + Arm64Ld2rV, + Arm64Ld2Vms, + Arm64Ld2Vss, + Arm64Ld3rV, + Arm64Ld3Vms, + Arm64Ld3Vss, + Arm64Ld4rV, + Arm64Ld4Vms, + Arm64Ld4Vss, + Arm64MlaVe, + Arm64MlaV, + Arm64MlsVe, + Arm64MlsV, + Arm64MoviV, + Arm64MrsFpcr, + Arm64MsrFpcr, + Arm64MrsFpsr, + Arm64MsrFpsr, + Arm64MulVe, + Arm64MulV, + Arm64MvniV, + Arm64NegS, + Arm64NegV, + Arm64NotV, + Arm64OrnV, + Arm64OrrVi, + Arm64OrrV, + Arm64PmullV, + Arm64PmulV, + Arm64RaddhnV, + Arm64RbitV, + Arm64Rev16V, + Arm64Rev32V, + Arm64Rev64V, + Arm64RshrnV, + Arm64RsubhnV, + Arm64SabalV, + Arm64SabaV, + Arm64SabdlV, + Arm64SabdV, + Arm64SadalpV, + Arm64SaddlpV, + Arm64SaddlvV, + Arm64SaddlV, + Arm64SaddwV, + Arm64ScvtfSFixed, + Arm64ScvtfVFixed, + Arm64ScvtfS, + Arm64ScvtfV, + Arm64ScvtfGpFixed, + Arm64ScvtfGp, + Arm64Sha1cV, + Arm64Sha1hV, + Arm64Sha1mV, + Arm64Sha1pV, + Arm64Sha1su0V, + Arm64Sha1su1V, + Arm64Sha256h2V, + Arm64Sha256hV, + Arm64Sha256su0V, + Arm64Sha256su1V, + Arm64ShaddV, + Arm64ShllV, + Arm64ShlS, + Arm64ShlV, + Arm64ShrnV, + Arm64ShsubV, + Arm64SliS, + Arm64SliV, + Arm64SmaxpV, + Arm64SmaxvV, + Arm64SmaxV, + Arm64SminpV, + Arm64SminvV, + Arm64SminV, + Arm64SmlalVe, + Arm64SmlalV, + Arm64SmlslVe, + Arm64SmlslV, + Arm64SmovV, + Arm64SmullVe, + Arm64SmullV, + Arm64SqabsS, + Arm64SqabsV, + Arm64SqaddS, + Arm64SqaddV, + Arm64SqdmlalSe, + Arm64SqdmlalVe, + Arm64SqdmlalS, + Arm64SqdmlalV, + Arm64SqdmlslSe, + Arm64SqdmlslVe, + Arm64SqdmlslS, + Arm64SqdmlslV, + Arm64SqdmulhSe, + Arm64SqdmulhVe, + Arm64SqdmulhS, + Arm64SqdmulhV, + Arm64SqdmullSe, + Arm64SqdmullVe, + Arm64SqdmullS, + Arm64SqdmullV, + Arm64SqnegS, + Arm64SqnegV, + Arm64SqrdmulhSe, + Arm64SqrdmulhVe, + Arm64SqrdmulhS, + Arm64SqrdmulhV, + Arm64SqrshlS, + Arm64SqrshlV, + Arm64SqrshrnS, + Arm64SqrshrnV, + Arm64SqrshrunS, + Arm64SqrshrunV, + Arm64SqshluS, + Arm64SqshluV, + Arm64SqshlSi, + Arm64SqshlVi, + Arm64SqshlS, + Arm64SqshlV, + Arm64SqshrnS, + Arm64SqshrnV, + Arm64SqshrunS, + Arm64SqshrunV, + Arm64SqsubS, + Arm64SqsubV, + Arm64SqxtnS, + Arm64SqxtnV, + Arm64SqxtunS, + Arm64SqxtunV, + Arm64SrhaddV, + Arm64SriS, + Arm64SriV, + Arm64SrshlS, + Arm64SrshlV, + Arm64SrshrS, + Arm64SrshrV, + Arm64SrsraS, + Arm64SrsraV, + Arm64SshllV, + Arm64SshlS, + Arm64SshlV, + Arm64SshrS, + Arm64SshrV, + Arm64SsraS, + Arm64SsraV, + Arm64SsublV, + Arm64SsubwV, + Arm64St1Vms, + Arm64St1Vss, + Arm64St2Vms, + Arm64St2Vss, + Arm64St3Vms, + Arm64St3Vss, + Arm64St4Vms, + Arm64St4Vss, + Arm64SubhnV, + Arm64SubS, + Arm64SubV, + Arm64SuqaddS, + Arm64SuqaddV, + Arm64TblV, + Arm64TbxV, + Arm64Trn1V, + Arm64Trn2V, + Arm64UabalV, + Arm64UabaV, + Arm64UabdlV, + Arm64UabdV, + Arm64UadalpV, + Arm64UaddlpV, + Arm64UaddlvV, + Arm64UaddlV, + Arm64UaddwV, + Arm64UcvtfSFixed, + Arm64UcvtfVFixed, + Arm64UcvtfS, + Arm64UcvtfV, + Arm64UcvtfGpFixed, + Arm64UcvtfGp, + Arm64UhaddV, + Arm64UhsubV, + Arm64UmaxpV, + Arm64UmaxvV, + Arm64UmaxV, + Arm64UminpV, + Arm64UminvV, + Arm64UminV, + Arm64UmlalVe, + Arm64UmlalV, + Arm64UmlslVe, + Arm64UmlslV, + Arm64UmovV, + Arm64UmullVe, + Arm64UmullV, + Arm64UqaddS, + Arm64UqaddV, + Arm64UqrshlS, + Arm64UqrshlV, + Arm64UqrshrnS, + Arm64UqrshrnV, + Arm64UqshlSi, + Arm64UqshlVi, + Arm64UqshlS, + Arm64UqshlV, + Arm64UqshrnS, + Arm64UqshrnV, + Arm64UqsubS, + Arm64UqsubV, + Arm64UqxtnS, + Arm64UqxtnV, + Arm64UrecpeV, + Arm64UrhaddV, + Arm64UrshlS, + Arm64UrshlV, + Arm64UrshrS, + Arm64UrshrV, + Arm64UrsqrteV, + Arm64UrsraS, + Arm64UrsraV, + Arm64UshllV, + Arm64UshlS, + Arm64UshlV, + Arm64UshrS, + Arm64UshrV, + Arm64UsqaddS, + Arm64UsqaddV, + Arm64UsraS, + Arm64UsraV, + Arm64UsublV, + Arm64UsubwV, + Arm64Uzp1V, + Arm64Uzp2V, + Arm64XtnV, + Arm64Zip1V, + Arm64Zip2V, + + Arm64VTypeShift = 13, + Arm64VTypeMask = 1 << Arm64VTypeShift, + Arm64V64 = 0 << Arm64VTypeShift, + Arm64V128 = 1 << Arm64VTypeShift, + + Arm64VSizeShift = 14, + Arm64VSizeMask = 3 << Arm64VSizeShift, + Arm64VFloat = 0 << Arm64VSizeShift, + Arm64VDouble = 1 << Arm64VSizeShift, + Arm64VByte = 0 << Arm64VSizeShift, + Arm64VHWord = 1 << Arm64VSizeShift, + Arm64VWord = 2 << Arm64VSizeShift, + Arm64VDWord = 3 << Arm64VSizeShift + } +} \ No newline at end of file diff --git a/src/ARMeilleure/IntermediateRepresentation/IntrusiveList.cs b/src/ARMeilleure/IntermediateRepresentation/IntrusiveList.cs new file mode 100644 index 00000000..184df87c --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/IntrusiveList.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.IntermediateRepresentation +{ + /// + /// Represents a efficient linked list that stores the pointer on the object directly and does not allocate. + /// + /// Type of the list items + class IntrusiveList where T : IEquatable, IIntrusiveListNode + { + /// + /// First item of the list, or null if empty. + /// + public T First { get; private set; } + + /// + /// Last item of the list, or null if empty. + /// + public T Last { get; private set; } + + /// + /// Total number of items on the list. + /// + public int Count { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// is not pointer sized. + public IntrusiveList() + { + if (Unsafe.SizeOf() != IntPtr.Size) + { + throw new ArgumentException("T must be a reference type or a pointer sized struct."); + } + } + + /// + /// Adds a item as the first item of the list. + /// + /// Item to be added + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T AddFirst(T newNode) + { + if (!EqualsNull(First)) + { + return AddBefore(First, newNode); + } + else + { + Debug.Assert(EqualsNull(newNode.ListPrevious)); + Debug.Assert(EqualsNull(newNode.ListNext)); + Debug.Assert(EqualsNull(Last)); + + First = newNode; + Last = newNode; + + Debug.Assert(Count == 0); + + Count = 1; + + return newNode; + } + } + + /// + /// Adds a item as the last item of the list. + /// + /// Item to be added + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T AddLast(T newNode) + { + if (!EqualsNull(Last)) + { + return AddAfter(Last, newNode); + } + else + { + Debug.Assert(EqualsNull(newNode.ListPrevious)); + Debug.Assert(EqualsNull(newNode.ListNext)); + Debug.Assert(EqualsNull(First)); + + First = newNode; + Last = newNode; + + Debug.Assert(Count == 0); + + Count = 1; + + return newNode; + } + } + + /// + /// Adds a item before a existing item on the list. + /// + /// Item on the list that will succeed the new item + /// Item to be added + /// New item + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T AddBefore(T node, T newNode) + { + Debug.Assert(EqualsNull(newNode.ListPrevious)); + Debug.Assert(EqualsNull(newNode.ListNext)); + + newNode.ListPrevious = node.ListPrevious; + newNode.ListNext = node; + + node.ListPrevious = newNode; + + if (!EqualsNull(newNode.ListPrevious)) + { + newNode.ListPrevious.ListNext = newNode; + } + + if (Equals(First, node)) + { + First = newNode; + } + + Count++; + + return newNode; + } + + /// + /// Adds a item after a existing item on the list. + /// + /// Item on the list that will preceed the new item + /// Item to be added + /// New item + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T AddAfter(T node, T newNode) + { + Debug.Assert(EqualsNull(newNode.ListPrevious)); + Debug.Assert(EqualsNull(newNode.ListNext)); + + newNode.ListPrevious = node; + newNode.ListNext = node.ListNext; + + node.ListNext = newNode; + + if (!EqualsNull(newNode.ListNext)) + { + newNode.ListNext.ListPrevious = newNode; + } + + if (Equals(Last, node)) + { + Last = newNode; + } + + Count++; + + return newNode; + } + + /// + /// Removes a item from the list. + /// + /// The item to be removed + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Remove(T node) + { + if (!EqualsNull(node.ListPrevious)) + { + node.ListPrevious.ListNext = node.ListNext; + } + else + { + Debug.Assert(Equals(First, node)); + + First = node.ListNext; + } + + if (!EqualsNull(node.ListNext)) + { + node.ListNext.ListPrevious = node.ListPrevious; + } + else + { + Debug.Assert(Equals(Last, node)); + + Last = node.ListPrevious; + } + + node.ListPrevious = default; + node.ListNext = default; + + Count--; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool EqualsNull(T a) + { + return EqualityComparer.Default.Equals(a, default); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool Equals(T a, T b) + { + return EqualityComparer.Default.Equals(a, b); + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/MemoryOperand.cs b/src/ARMeilleure/IntermediateRepresentation/MemoryOperand.cs new file mode 100644 index 00000000..07d2633b --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/MemoryOperand.cs @@ -0,0 +1,54 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.IntermediateRepresentation +{ + unsafe struct MemoryOperand + { + private struct Data + { +#pragma warning disable CS0649 + public byte Kind; + public byte Type; +#pragma warning restore CS0649 + public byte Scale; + public Operand BaseAddress; + public Operand Index; + public int Displacement; + } + + private Data* _data; + + public MemoryOperand(Operand operand) + { + Debug.Assert(operand.Kind == OperandKind.Memory); + + _data = (Data*)Unsafe.As(ref operand); + } + + public Operand BaseAddress + { + get => _data->BaseAddress; + set => _data->BaseAddress = value; + } + + public Operand Index + { + get => _data->Index; + set => _data->Index = value; + } + + public Multiplier Scale + { + get => (Multiplier)_data->Scale; + set => _data->Scale = (byte)value; + } + + public int Displacement + { + get => _data->Displacement; + set => _data->Displacement = value; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/IntermediateRepresentation/Multiplier.cs b/src/ARMeilleure/IntermediateRepresentation/Multiplier.cs new file mode 100644 index 00000000..d6bc7d99 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/Multiplier.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum Multiplier + { + x1 = 0, + x2 = 1, + x4 = 2, + x8 = 3, + x16 = 4 + } +} \ No newline at end of file diff --git a/src/ARMeilleure/IntermediateRepresentation/Operand.cs b/src/ARMeilleure/IntermediateRepresentation/Operand.cs new file mode 100644 index 00000000..9e8de3ba --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/Operand.cs @@ -0,0 +1,594 @@ +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.Common; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.IntermediateRepresentation +{ + unsafe struct Operand : IEquatable + { + internal struct Data + { + public byte Kind; + public byte Type; + public byte SymbolType; + public byte Padding; // Unused space. + public ushort AssignmentsCount; + public ushort AssignmentsCapacity; + public uint UsesCount; + public uint UsesCapacity; + public Operation* Assignments; + public Operation* Uses; + public ulong Value; + public ulong SymbolValue; + } + + private Data* _data; + + public OperandKind Kind + { + get => (OperandKind)_data->Kind; + private set => _data->Kind = (byte)value; + } + + public OperandType Type + { + get => (OperandType)_data->Type; + private set => _data->Type = (byte)value; + } + + public ulong Value + { + get => _data->Value; + private set => _data->Value = value; + } + + public Symbol Symbol + { + get + { + Debug.Assert(Kind != OperandKind.Memory); + + return new Symbol((SymbolType)_data->SymbolType, _data->SymbolValue); + } + private set + { + Debug.Assert(Kind != OperandKind.Memory); + + if (value.Type == SymbolType.None) + { + _data->SymbolType = (byte)SymbolType.None; + } + else + { + _data->SymbolType = (byte)value.Type; + _data->SymbolValue = value.Value; + } + } + } + + public ReadOnlySpan Assignments + { + get + { + Debug.Assert(Kind != OperandKind.Memory); + + return new ReadOnlySpan(_data->Assignments, _data->AssignmentsCount); + } + } + + public ReadOnlySpan Uses + { + get + { + Debug.Assert(Kind != OperandKind.Memory); + + return new ReadOnlySpan(_data->Uses, (int)_data->UsesCount); + } + } + + public int UsesCount => (int)_data->UsesCount; + public int AssignmentsCount => _data->AssignmentsCount; + + public bool Relocatable => Symbol.Type != SymbolType.None; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Register GetRegister() + { + Debug.Assert(Kind == OperandKind.Register); + + return new Register((int)Value & 0xffffff, (RegisterType)(Value >> 24)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public MemoryOperand GetMemory() + { + Debug.Assert(Kind == OperandKind.Memory); + + return new MemoryOperand(this); + } + + public int GetLocalNumber() + { + Debug.Assert(Kind == OperandKind.LocalVariable); + + return (int)Value; + } + + public byte AsByte() + { + return (byte)Value; + } + + public short AsInt16() + { + return (short)Value; + } + + public int AsInt32() + { + return (int)Value; + } + + public long AsInt64() + { + return (long)Value; + } + + public float AsFloat() + { + return BitConverter.Int32BitsToSingle((int)Value); + } + + public double AsDouble() + { + return BitConverter.Int64BitsToDouble((long)Value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ref ulong GetValueUnsafe() + { + return ref _data->Value; + } + + internal void NumberLocal(int number) + { + if (Kind != OperandKind.LocalVariable) + { + throw new InvalidOperationException("The operand is not a local variable."); + } + + Value = (ulong)number; + } + + public void AddAssignment(Operation operation) + { + if (Kind == OperandKind.LocalVariable) + { + Add(operation, ref _data->Assignments, ref _data->AssignmentsCount, ref _data->AssignmentsCapacity); + } + else if (Kind == OperandKind.Memory) + { + MemoryOperand memOp = GetMemory(); + Operand addr = memOp.BaseAddress; + Operand index = memOp.Index; + + if (addr != default) + { + Add(operation, ref addr._data->Assignments, ref addr._data->AssignmentsCount, ref addr._data->AssignmentsCapacity); + } + + if (index != default) + { + Add(operation, ref index._data->Assignments, ref index._data->AssignmentsCount, ref index._data->AssignmentsCapacity); + } + } + } + + public void RemoveAssignment(Operation operation) + { + if (Kind == OperandKind.LocalVariable) + { + Remove(operation, ref _data->Assignments, ref _data->AssignmentsCount); + } + else if (Kind == OperandKind.Memory) + { + MemoryOperand memOp = GetMemory(); + Operand addr = memOp.BaseAddress; + Operand index = memOp.Index; + + if (addr != default) + { + Remove(operation, ref addr._data->Assignments, ref addr._data->AssignmentsCount); + } + + if (index != default) + { + Remove(operation, ref index._data->Assignments, ref index._data->AssignmentsCount); + } + } + } + + public void AddUse(Operation operation) + { + if (Kind == OperandKind.LocalVariable) + { + Add(operation, ref _data->Uses, ref _data->UsesCount, ref _data->UsesCapacity); + } + else if (Kind == OperandKind.Memory) + { + MemoryOperand memOp = GetMemory(); + Operand addr = memOp.BaseAddress; + Operand index = memOp.Index; + + if (addr != default) + { + Add(operation, ref addr._data->Uses, ref addr._data->UsesCount, ref addr._data->UsesCapacity); + } + + if (index != default) + { + Add(operation, ref index._data->Uses, ref index._data->UsesCount, ref index._data->UsesCapacity); + } + } + } + + public void RemoveUse(Operation operation) + { + if (Kind == OperandKind.LocalVariable) + { + Remove(operation, ref _data->Uses, ref _data->UsesCount); + } + else if (Kind == OperandKind.Memory) + { + MemoryOperand memOp = GetMemory(); + Operand addr = memOp.BaseAddress; + Operand index = memOp.Index; + + if (addr != default) + { + Remove(operation, ref addr._data->Uses, ref addr._data->UsesCount); + } + + if (index != default) + { + Remove(operation, ref index._data->Uses, ref index._data->UsesCount); + } + } + } + + public Span GetUses(ref Span buffer) + { + ReadOnlySpan uses = Uses; + + if (buffer.Length < uses.Length) + { + buffer = Allocators.Default.AllocateSpan((uint)uses.Length); + } + + uses.CopyTo(buffer); + + return buffer.Slice(0, uses.Length); + } + + private static void New(ref T* data, ref ushort count, ref ushort capacity, ushort initialCapacity) where T : unmanaged + { + count = 0; + capacity = initialCapacity; + data = Allocators.References.Allocate(initialCapacity); + } + + private static void New(ref T* data, ref uint count, ref uint capacity, uint initialCapacity) where T : unmanaged + { + count = 0; + capacity = initialCapacity; + data = Allocators.References.Allocate(initialCapacity); + } + + private static void Add(T item, ref T* data, ref ushort count, ref ushort capacity) where T : unmanaged + { + if (count < capacity) + { + data[(uint)count++] = item; + + return; + } + + // Could not add item in the fast path, fallback onto the slow path. + ExpandAdd(item, ref data, ref count, ref capacity); + + static void ExpandAdd(T item, ref T* data, ref ushort count, ref ushort capacity) + { + ushort newCount = checked((ushort)(count + 1)); + ushort newCapacity = (ushort)Math.Min(capacity * 2, ushort.MaxValue); + + var oldSpan = new Span(data, count); + + capacity = newCapacity; + data = Allocators.References.Allocate(capacity); + + oldSpan.CopyTo(new Span(data, count)); + + data[count] = item; + count = newCount; + } + } + + private static void Add(T item, ref T* data, ref uint count, ref uint capacity) where T : unmanaged + { + if (count < capacity) + { + data[count++] = item; + + return; + } + + // Could not add item in the fast path, fallback onto the slow path. + ExpandAdd(item, ref data, ref count, ref capacity); + + static void ExpandAdd(T item, ref T* data, ref uint count, ref uint capacity) + { + uint newCount = checked(count + 1); + uint newCapacity = (uint)Math.Min(capacity * 2, int.MaxValue); + + if (newCapacity <= capacity) + { + throw new OverflowException(); + } + + var oldSpan = new Span(data, (int)count); + + capacity = newCapacity; + data = Allocators.References.Allocate(capacity); + + oldSpan.CopyTo(new Span(data, (int)count)); + + data[count] = item; + count = newCount; + } + } + + private static void Remove(in T item, ref T* data, ref ushort count) where T : unmanaged + { + var span = new Span(data, count); + + for (int i = 0; i < span.Length; i++) + { + if (EqualityComparer.Default.Equals(span[i], item)) + { + if (i + 1 < count) + { + span.Slice(i + 1).CopyTo(span.Slice(i)); + } + + count--; + + return; + } + } + } + + private static void Remove(in T item, ref T* data, ref uint count) where T : unmanaged + { + var span = new Span(data, (int)count); + + for (int i = 0; i < span.Length; i++) + { + if (EqualityComparer.Default.Equals(span[i], item)) + { + if (i + 1 < count) + { + span.Slice(i + 1).CopyTo(span.Slice(i)); + } + + count--; + + return; + } + } + } + + public override int GetHashCode() + { + return ((ulong)_data).GetHashCode(); + } + + public bool Equals(Operand operand) + { + return operand._data == _data; + } + + public override bool Equals(object obj) + { + return obj is Operand operand && Equals(operand); + } + + public static bool operator ==(Operand a, Operand b) + { + return a.Equals(b); + } + + public static bool operator !=(Operand a, Operand b) + { + return !a.Equals(b); + } + + public static class Factory + { + private const int InternTableSize = 256; + private const int InternTableProbeLength = 8; + + [ThreadStatic] + private static Data* _internTable; + + private static Data* InternTable + { + get + { + if (_internTable == null) + { + _internTable = (Data*)NativeAllocator.Instance.Allocate((uint)sizeof(Data) * InternTableSize); + + // Make sure the table is zeroed. + new Span(_internTable, InternTableSize).Clear(); + } + + return _internTable; + } + } + + private static Operand Make(OperandKind kind, OperandType type, ulong value, Symbol symbol = default) + { + Debug.Assert(kind != OperandKind.None); + + Data* data = null; + + // If constant or register, then try to look up in the intern table before allocating. + if (kind == OperandKind.Constant || kind == OperandKind.Register) + { + uint hash = (uint)HashCode.Combine(kind, type, value); + + // Look in the next InternTableProbeLength slots for a match. + for (uint i = 0; i < InternTableProbeLength; i++) + { + Operand interned = new(); + interned._data = &InternTable[(hash + i) % InternTableSize]; + + // If slot matches the allocation request then return that slot. + if (interned.Kind == kind && interned.Type == type && interned.Value == value && interned.Symbol == symbol) + { + return interned; + } + // Otherwise if the slot is not occupied, we store in that slot. + else if (interned.Kind == OperandKind.None) + { + data = interned._data; + + break; + } + } + } + + // If we could not get a slot from the intern table, we allocate somewhere else and store there. + if (data == null) + { + data = Allocators.Operands.Allocate(); + } + + *data = default; + + Operand result = new(); + result._data = data; + result.Value = value; + result.Kind = kind; + result.Type = type; + + if (kind != OperandKind.Memory) + { + result.Symbol = symbol; + } + + // If local variable, then the use and def list is initialized with default sizes. + if (kind == OperandKind.LocalVariable) + { + New(ref result._data->Assignments, ref result._data->AssignmentsCount, ref result._data->AssignmentsCapacity, 1); + New(ref result._data->Uses, ref result._data->UsesCount, ref result._data->UsesCapacity, 4); + } + + return result; + } + + public static Operand Const(OperandType type, long value) + { + Debug.Assert(type is OperandType.I32 or OperandType.I64); + + return type == OperandType.I32 ? Const((int)value) : Const(value); + } + + public static Operand Const(bool value) + { + return Const(value ? 1 : 0); + } + + public static Operand Const(int value) + { + return Const((uint)value); + } + + public static Operand Const(uint value) + { + return Make(OperandKind.Constant, OperandType.I32, value); + } + + public static Operand Const(long value) + { + return Const(value, symbol: default); + } + + public static Operand Const(ref T reference, Symbol symbol = default) + { + return Const((long)Unsafe.AsPointer(ref reference), symbol); + } + + public static Operand Const(long value, Symbol symbol) + { + return Make(OperandKind.Constant, OperandType.I64, (ulong)value, symbol); + } + + public static Operand Const(ulong value) + { + return Make(OperandKind.Constant, OperandType.I64, value); + } + + public static Operand ConstF(float value) + { + return Make(OperandKind.Constant, OperandType.FP32, (ulong)BitConverter.SingleToInt32Bits(value)); + } + + public static Operand ConstF(double value) + { + return Make(OperandKind.Constant, OperandType.FP64, (ulong)BitConverter.DoubleToInt64Bits(value)); + } + + public static Operand Label() + { + return Make(OperandKind.Label, OperandType.None, 0); + } + + public static Operand Local(OperandType type) + { + return Make(OperandKind.LocalVariable, type, 0); + } + + public static Operand Register(int index, RegisterType regType, OperandType type) + { + return Make(OperandKind.Register, type, (ulong)((int)regType << 24 | index)); + } + + public static Operand Undef() + { + return Make(OperandKind.Undefined, OperandType.None, 0); + } + + public static Operand MemoryOp( + OperandType type, + Operand baseAddress, + Operand index = default, + Multiplier scale = Multiplier.x1, + int displacement = 0) + { + Operand result = Make(OperandKind.Memory, type, 0); + + MemoryOperand memory = result.GetMemory(); + memory.BaseAddress = baseAddress; + memory.Index = index; + memory.Scale = scale; + memory.Displacement = displacement; + + return result; + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/IntermediateRepresentation/OperandKind.cs b/src/ARMeilleure/IntermediateRepresentation/OperandKind.cs new file mode 100644 index 00000000..adb83561 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/OperandKind.cs @@ -0,0 +1,13 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum OperandKind + { + None, + Constant, + Label, + LocalVariable, + Memory, + Register, + Undefined + } +} \ No newline at end of file diff --git a/src/ARMeilleure/IntermediateRepresentation/OperandType.cs b/src/ARMeilleure/IntermediateRepresentation/OperandType.cs new file mode 100644 index 00000000..81b22cf5 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/OperandType.cs @@ -0,0 +1,65 @@ +using System; + +namespace ARMeilleure.IntermediateRepresentation +{ + enum OperandType + { + None, + I32, + I64, + FP32, + FP64, + V128 + } + + static class OperandTypeExtensions + { + public static bool IsInteger(this OperandType type) + { + return type == OperandType.I32 || + type == OperandType.I64; + } + + public static RegisterType ToRegisterType(this OperandType type) + { + switch (type) + { + case OperandType.FP32: return RegisterType.Vector; + case OperandType.FP64: return RegisterType.Vector; + case OperandType.I32: return RegisterType.Integer; + case OperandType.I64: return RegisterType.Integer; + case OperandType.V128: return RegisterType.Vector; + } + + throw new InvalidOperationException($"Invalid operand type \"{type}\"."); + } + + public static int GetSizeInBytes(this OperandType type) + { + switch (type) + { + case OperandType.FP32: return 4; + case OperandType.FP64: return 8; + case OperandType.I32: return 4; + case OperandType.I64: return 8; + case OperandType.V128: return 16; + } + + throw new InvalidOperationException($"Invalid operand type \"{type}\"."); + } + + public static int GetSizeInBytesLog2(this OperandType type) + { + switch (type) + { + case OperandType.FP32: return 2; + case OperandType.FP64: return 3; + case OperandType.I32: return 2; + case OperandType.I64: return 3; + case OperandType.V128: return 4; + } + + throw new InvalidOperationException($"Invalid operand type \"{type}\"."); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/IntermediateRepresentation/Operation.cs b/src/ARMeilleure/IntermediateRepresentation/Operation.cs new file mode 100644 index 00000000..c71e143c --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/Operation.cs @@ -0,0 +1,376 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.IntermediateRepresentation +{ + unsafe struct Operation : IEquatable, IIntrusiveListNode + { + internal struct Data + { + public ushort Instruction; + public ushort Intrinsic; + public ushort SourcesCount; + public ushort DestinationsCount; + public Operation ListPrevious; + public Operation ListNext; + public Operand* Destinations; + public Operand* Sources; + } + + private Data* _data; + + public Instruction Instruction + { + get => (Instruction)_data->Instruction; + private set => _data->Instruction = (ushort)value; + } + + public Intrinsic Intrinsic + { + get => (Intrinsic)_data->Intrinsic; + private set => _data->Intrinsic = (ushort)value; + } + + public Operation ListPrevious + { + get => _data->ListPrevious; + set => _data->ListPrevious = value; + } + + public Operation ListNext + { + get => _data->ListNext; + set => _data->ListNext = value; + } + + public Operand Destination + { + get => _data->DestinationsCount != 0 ? GetDestination(0) : default; + set => SetDestination(value); + } + + public int DestinationsCount => _data->DestinationsCount; + public int SourcesCount => _data->SourcesCount; + + internal Span DestinationsUnsafe => new(_data->Destinations, _data->DestinationsCount); + internal Span SourcesUnsafe => new(_data->Sources, _data->SourcesCount); + + public PhiOperation AsPhi() + { + Debug.Assert(Instruction == Instruction.Phi); + + return new PhiOperation(this); + } + + public Operand GetDestination(int index) + { + return DestinationsUnsafe[index]; + } + + public Operand GetSource(int index) + { + return SourcesUnsafe[index]; + } + + public void SetDestination(int index, Operand dest) + { + ref Operand curDest = ref DestinationsUnsafe[index]; + + RemoveAssignment(curDest); + AddAssignment(dest); + + curDest = dest; + } + + public void SetSource(int index, Operand src) + { + ref Operand curSrc = ref SourcesUnsafe[index]; + + RemoveUse(curSrc); + AddUse(src); + + curSrc = src; + } + + private void RemoveOldDestinations() + { + for (int i = 0; i < _data->DestinationsCount; i++) + { + RemoveAssignment(_data->Destinations[i]); + } + } + + public void SetDestination(Operand dest) + { + RemoveOldDestinations(); + + if (dest == default) + { + _data->DestinationsCount = 0; + } + else + { + EnsureCapacity(ref _data->Destinations, ref _data->DestinationsCount, 1); + + _data->Destinations[0] = dest; + + AddAssignment(dest); + } + } + + public void SetDestinations(Operand[] dests) + { + RemoveOldDestinations(); + + EnsureCapacity(ref _data->Destinations, ref _data->DestinationsCount, dests.Length); + + for (int index = 0; index < dests.Length; index++) + { + Operand newOp = dests[index]; + + _data->Destinations[index] = newOp; + + AddAssignment(newOp); + } + } + + private void RemoveOldSources() + { + for (int index = 0; index < _data->SourcesCount; index++) + { + RemoveUse(_data->Sources[index]); + } + } + + public void SetSource(Operand src) + { + RemoveOldSources(); + + if (src == default) + { + _data->SourcesCount = 0; + } + else + { + EnsureCapacity(ref _data->Sources, ref _data->SourcesCount, 1); + + _data->Sources[0] = src; + + AddUse(src); + } + } + + public void SetSources(Operand[] srcs) + { + RemoveOldSources(); + + EnsureCapacity(ref _data->Sources, ref _data->SourcesCount, srcs.Length); + + for (int index = 0; index < srcs.Length; index++) + { + Operand newOp = srcs[index]; + + _data->Sources[index] = newOp; + + AddUse(newOp); + } + } + + public void TurnIntoCopy(Operand source) + { + Instruction = Instruction.Copy; + + SetSource(source); + } + + private void AddAssignment(Operand op) + { + if (op != default) + { + op.AddAssignment(this); + } + } + + private void RemoveAssignment(Operand op) + { + if (op != default) + { + op.RemoveAssignment(this); + } + } + + private void AddUse(Operand op) + { + if (op != default) + { + op.AddUse(this); + } + } + + private void RemoveUse(Operand op) + { + if (op != default) + { + op.RemoveUse(this); + } + } + + public bool Equals(Operation operation) + { + return operation._data == _data; + } + + public override bool Equals(object obj) + { + return obj is Operation operation && Equals(operation); + } + + public override int GetHashCode() + { + return HashCode.Combine((IntPtr)_data); + } + + public static bool operator ==(Operation a, Operation b) + { + return a.Equals(b); + } + + public static bool operator !=(Operation a, Operation b) + { + return !a.Equals(b); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void EnsureCapacity(ref Operand* list, ref ushort capacity, int newCapacity) + { + if (newCapacity > ushort.MaxValue) + { + ThrowOverflow(newCapacity); + } + // We only need to allocate a new buffer if we're increasing the size. + else if (newCapacity > capacity) + { + list = Allocators.References.Allocate((uint)newCapacity); + } + + capacity = (ushort)newCapacity; + } + + private static void ThrowOverflow(int count) => + throw new OverflowException($"Exceeded maximum size for Source or Destinations. Required {count}."); + + public static class Factory + { + private static Operation Make(Instruction inst, int destCount, int srcCount) + { + Data* data = Allocators.Operations.Allocate(); + *data = default; + + Operation result = new(); + result._data = data; + result.Instruction = inst; + + EnsureCapacity(ref result._data->Destinations, ref result._data->DestinationsCount, destCount); + EnsureCapacity(ref result._data->Sources, ref result._data->SourcesCount, srcCount); + + result.DestinationsUnsafe.Clear(); + result.SourcesUnsafe.Clear(); + + return result; + } + + public static Operation Operation(Instruction inst, Operand dest) + { + Operation result = Make(inst, 0, 0); + result.SetDestination(dest); + return result; + } + + public static Operation Operation(Instruction inst, Operand dest, Operand src0) + { + Operation result = Make(inst, 0, 1); + result.SetDestination(dest); + result.SetSource(0, src0); + return result; + } + + public static Operation Operation(Instruction inst, Operand dest, Operand src0, Operand src1) + { + Operation result = Make(inst, 0, 2); + result.SetDestination(dest); + result.SetSource(0, src0); + result.SetSource(1, src1); + return result; + } + + public static Operation Operation(Instruction inst, Operand dest, Operand src0, Operand src1, Operand src2) + { + Operation result = Make(inst, 0, 3); + result.SetDestination(dest); + result.SetSource(0, src0); + result.SetSource(1, src1); + result.SetSource(2, src2); + return result; + } + + public static Operation Operation(Instruction inst, Operand dest, int srcCount) + { + Operation result = Make(inst, 0, srcCount); + result.SetDestination(dest); + return result; + } + + public static Operation Operation(Instruction inst, Operand dest, Operand[] srcs) + { + Operation result = Make(inst, 0, srcs.Length); + + result.SetDestination(dest); + + for (int index = 0; index < srcs.Length; index++) + { + result.SetSource(index, srcs[index]); + } + + return result; + } + + public static Operation Operation(Intrinsic intrin, Operand dest, params Operand[] srcs) + { + Operation result = Make(Instruction.Extended, 0, srcs.Length); + + result.Intrinsic = intrin; + result.SetDestination(dest); + + for (int index = 0; index < srcs.Length; index++) + { + result.SetSource(index, srcs[index]); + } + + return result; + } + + public static Operation Operation(Instruction inst, Operand[] dests, Operand[] srcs) + { + Operation result = Make(inst, dests.Length, srcs.Length); + + for (int index = 0; index < dests.Length; index++) + { + result.SetDestination(index, dests[index]); + } + + for (int index = 0; index < srcs.Length; index++) + { + result.SetSource(index, srcs[index]); + } + + return result; + } + + public static Operation PhiOperation(Operand dest, int srcCount) + { + return Operation(Instruction.Phi, dest, srcCount * 2); + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/IntermediateRepresentation/PhiOperation.cs b/src/ARMeilleure/IntermediateRepresentation/PhiOperation.cs new file mode 100644 index 00000000..d2a3cf21 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/PhiOperation.cs @@ -0,0 +1,37 @@ +using ARMeilleure.Translation; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.IntermediateRepresentation +{ + readonly struct PhiOperation + { + private readonly Operation _operation; + + public PhiOperation(Operation operation) + { + _operation = operation; + } + + public int SourcesCount => _operation.SourcesCount / 2; + + public BasicBlock GetBlock(ControlFlowGraph cfg, int index) + { + return cfg.PostOrderBlocks[cfg.PostOrderMap[_operation.GetSource(index * 2).AsInt32()]]; + } + + public void SetBlock(int index, BasicBlock block) + { + _operation.SetSource(index * 2, Const(block.Index)); + } + + public Operand GetSource(int index) + { + return _operation.GetSource(index * 2 + 1); + } + + public void SetSource(int index, Operand operand) + { + _operation.SetSource(index * 2 + 1, operand); + } + } +} diff --git a/src/ARMeilleure/IntermediateRepresentation/Register.cs b/src/ARMeilleure/IntermediateRepresentation/Register.cs new file mode 100644 index 00000000..241e4d13 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/Register.cs @@ -0,0 +1,43 @@ +using System; + +namespace ARMeilleure.IntermediateRepresentation +{ + readonly struct Register : IEquatable + { + public int Index { get; } + + public RegisterType Type { get; } + + public Register(int index, RegisterType type) + { + Index = index; + Type = type; + } + + public override int GetHashCode() + { + return (ushort)Index | ((int)Type << 16); + } + + public static bool operator ==(Register x, Register y) + { + return x.Equals(y); + } + + public static bool operator !=(Register x, Register y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is Register reg && Equals(reg); + } + + public bool Equals(Register other) + { + return other.Index == Index && + other.Type == Type; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/IntermediateRepresentation/RegisterType.cs b/src/ARMeilleure/IntermediateRepresentation/RegisterType.cs new file mode 100644 index 00000000..88ac6c12 --- /dev/null +++ b/src/ARMeilleure/IntermediateRepresentation/RegisterType.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.IntermediateRepresentation +{ + enum RegisterType + { + Integer, + Vector, + Flag, + FpFlag + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Memory/IJitMemoryAllocator.cs b/src/ARMeilleure/Memory/IJitMemoryAllocator.cs new file mode 100644 index 00000000..19b696b0 --- /dev/null +++ b/src/ARMeilleure/Memory/IJitMemoryAllocator.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Memory +{ + public interface IJitMemoryAllocator + { + IJitMemoryBlock Allocate(ulong size); + IJitMemoryBlock Reserve(ulong size); + + ulong GetPageSize(); + } +} diff --git a/src/ARMeilleure/Memory/IJitMemoryBlock.cs b/src/ARMeilleure/Memory/IJitMemoryBlock.cs new file mode 100644 index 00000000..670f2862 --- /dev/null +++ b/src/ARMeilleure/Memory/IJitMemoryBlock.cs @@ -0,0 +1,14 @@ +using System; + +namespace ARMeilleure.Memory +{ + public interface IJitMemoryBlock : IDisposable + { + IntPtr Pointer { get; } + + bool Commit(ulong offset, ulong size); + + void MapAsRx(ulong offset, ulong size); + void MapAsRwx(ulong offset, ulong size); + } +} diff --git a/src/ARMeilleure/Memory/IMemoryManager.cs b/src/ARMeilleure/Memory/IMemoryManager.cs new file mode 100644 index 00000000..5eb1fadd --- /dev/null +++ b/src/ARMeilleure/Memory/IMemoryManager.cs @@ -0,0 +1,77 @@ +using System; + +namespace ARMeilleure.Memory +{ + public interface IMemoryManager + { + int AddressSpaceBits { get; } + + IntPtr PageTablePointer { get; } + + MemoryManagerType Type { get; } + + event Action UnmapEvent; + + /// + /// Reads data from CPU mapped memory. + /// + /// Type of the data being read + /// Virtual address of the data in memory + /// The data + T Read(ulong va) where T : unmanaged; + + /// + /// Reads data from CPU mapped memory, with read tracking + /// + /// Type of the data being read + /// Virtual address of the data in memory + /// The data + T ReadTracked(ulong va) where T : unmanaged; + + /// + /// Writes data to CPU mapped memory. + /// + /// Type of the data being written + /// Virtual address to write the data into + /// Data to be written + void Write(ulong va, T value) where T : unmanaged; + + /// + /// Gets a read-only span of data from CPU mapped memory. + /// + /// Virtual address of the data + /// Size of the data + /// True if read tracking is triggered on the span + /// A read-only span of the data + ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false); + + /// + /// Gets a reference for the given type at the specified virtual memory address. + /// + /// + /// The data must be located at a contiguous memory region. + /// + /// Type of the data to get the reference + /// Virtual address of the data + /// A reference to the data in memory + ref T GetRef(ulong va) where T : unmanaged; + + /// + /// Checks if the page at a given CPU virtual address is mapped. + /// + /// Virtual address to check + /// True if the address is mapped, false otherwise + bool IsMapped(ulong va); + + /// + /// Alerts the memory tracking that a given region has been read from or written to. + /// This should be called before read/write is performed. + /// + /// Virtual address of the region + /// Size of the region + /// True if the region was written, false if read + /// True if the access is precise, false otherwise + /// Optional ID of the handles that should not be signalled + void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null); + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Memory/InvalidAccessException.cs b/src/ARMeilleure/Memory/InvalidAccessException.cs new file mode 100644 index 00000000..ad540719 --- /dev/null +++ b/src/ARMeilleure/Memory/InvalidAccessException.cs @@ -0,0 +1,23 @@ +using System; + +namespace ARMeilleure.Memory +{ + class InvalidAccessException : Exception + { + public InvalidAccessException() + { + } + + public InvalidAccessException(ulong address) : base($"Invalid memory access at virtual address 0x{address:X16}.") + { + } + + public InvalidAccessException(string message) : base(message) + { + } + + public InvalidAccessException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/ARMeilleure/Memory/MemoryManagerType.cs b/src/ARMeilleure/Memory/MemoryManagerType.cs new file mode 100644 index 00000000..ce84ccaf --- /dev/null +++ b/src/ARMeilleure/Memory/MemoryManagerType.cs @@ -0,0 +1,41 @@ +namespace ARMeilleure.Memory +{ + /// + /// Indicates the type of a memory manager and the method it uses for memory mapping + /// and address translation. This controls the code generated for memory accesses on the JIT. + /// + public enum MemoryManagerType + { + /// + /// Complete software MMU implementation, the read/write methods are always called, + /// without any attempt to perform faster memory access. + /// + SoftwareMmu, + + /// + /// High level implementation using a software flat page table for address translation, + /// used to speed up address translation if possible without calling the read/write methods. + /// + SoftwarePageTable, + + /// + /// High level implementation with mappings managed by the host OS, effectively using hardware + /// page tables. No address translation is performed in software and the memory is just accessed directly. + /// + HostMapped, + + /// + /// Same as the host mapped memory manager type, but without masking the address within the address space. + /// Allows invalid access from JIT code to the rest of the program, but is faster. + /// + HostMappedUnsafe + } + + static class MemoryManagerTypeExtensions + { + public static bool IsHostMapped(this MemoryManagerType type) + { + return type == MemoryManagerType.HostMapped || type == MemoryManagerType.HostMappedUnsafe; + } + } +} diff --git a/src/ARMeilleure/Memory/ReservedRegion.cs b/src/ARMeilleure/Memory/ReservedRegion.cs new file mode 100644 index 00000000..2197afad --- /dev/null +++ b/src/ARMeilleure/Memory/ReservedRegion.cs @@ -0,0 +1,58 @@ +using System; + +namespace ARMeilleure.Memory +{ + class ReservedRegion + { + public const int DefaultGranularity = 65536; // Mapping granularity in Windows. + + public IJitMemoryBlock Block { get; } + + public IntPtr Pointer => Block.Pointer; + + private readonly ulong _maxSize; + private readonly ulong _sizeGranularity; + private ulong _currentSize; + + public ReservedRegion(IJitMemoryAllocator allocator, ulong maxSize, ulong granularity = 0) + { + if (granularity == 0) + { + granularity = DefaultGranularity; + } + + Block = allocator.Reserve(maxSize); + _maxSize = maxSize; + _sizeGranularity = granularity; + _currentSize = 0; + } + + public void ExpandIfNeeded(ulong desiredSize) + { + if (desiredSize > _maxSize) + { + throw new OutOfMemoryException(); + } + + if (desiredSize > _currentSize) + { + // Lock, and then check again. We only want to commit once. + lock (this) + { + if (desiredSize >= _currentSize) + { + ulong overflowBytes = desiredSize - _currentSize; + ulong moreToCommit = (((_sizeGranularity - 1) + overflowBytes) / _sizeGranularity) * _sizeGranularity; // Round up. + Block.Commit(_currentSize, moreToCommit); + _currentSize += moreToCommit; + } + } + } + } + + public void Dispose() + { + Block.Dispose(); + } + } +} diff --git a/src/ARMeilleure/Native/JitSupportDarwin.cs b/src/ARMeilleure/Native/JitSupportDarwin.cs new file mode 100644 index 00000000..7d6a8634 --- /dev/null +++ b/src/ARMeilleure/Native/JitSupportDarwin.cs @@ -0,0 +1,13 @@ +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace ARMeilleure.Native +{ + [SupportedOSPlatform("macos")] + public static partial class JitSupportDarwin + { + [LibraryImport("libarmeilleure-jitsupport", EntryPoint = "armeilleure_jit_memcpy")] + public static partial void Copy(IntPtr dst, IntPtr src, ulong n); + } +} diff --git a/src/ARMeilleure/Native/libs/libarmeilleure-jitsupport.dylib b/src/ARMeilleure/Native/libs/libarmeilleure-jitsupport.dylib new file mode 100644 index 00000000..c65b0a4e Binary files /dev/null and b/src/ARMeilleure/Native/libs/libarmeilleure-jitsupport.dylib differ diff --git a/src/ARMeilleure/Native/macos_jit_support/Makefile b/src/ARMeilleure/Native/macos_jit_support/Makefile new file mode 100644 index 00000000..d6da35d5 --- /dev/null +++ b/src/ARMeilleure/Native/macos_jit_support/Makefile @@ -0,0 +1,8 @@ +NAME = libarmeilleure-jitsupport.dylib + +all: ${NAME} + +${NAME}: + clang -O3 -dynamiclib support.c -o ${NAME} +clean: + rm -f ${NAME} diff --git a/src/ARMeilleure/Native/macos_jit_support/support.c b/src/ARMeilleure/Native/macos_jit_support/support.c new file mode 100644 index 00000000..1b13d906 --- /dev/null +++ b/src/ARMeilleure/Native/macos_jit_support/support.c @@ -0,0 +1,14 @@ +#include +#include +#include + +#include + +void armeilleure_jit_memcpy(void *dst, const void *src, size_t n) { + pthread_jit_write_protect_np(0); + memcpy(dst, src, n); + pthread_jit_write_protect_np(1); + + // Ensure that the instruction cache for this range is invalidated. + sys_icache_invalidate(dst, n); +} diff --git a/src/ARMeilleure/Optimizations.cs b/src/ARMeilleure/Optimizations.cs new file mode 100644 index 00000000..a84a4dc4 --- /dev/null +++ b/src/ARMeilleure/Optimizations.cs @@ -0,0 +1,68 @@ +using System.Runtime.Intrinsics.Arm; + +namespace ARMeilleure +{ + using Arm64HardwareCapabilities = ARMeilleure.CodeGen.Arm64.HardwareCapabilities; + using X86HardwareCapabilities = ARMeilleure.CodeGen.X86.HardwareCapabilities; + + public static class Optimizations + { + public static bool FastFP { get; set; } = true; + + public static bool AllowLcqInFunctionTable { get; set; } = true; + public static bool UseUnmanagedDispatchLoop { get; set; } = true; + + public static bool UseAdvSimdIfAvailable { get; set; } = true; + public static bool UseArm64PmullIfAvailable { get; set; } = true; + + public static bool UseSseIfAvailable { get; set; } = true; + public static bool UseSse2IfAvailable { get; set; } = true; + public static bool UseSse3IfAvailable { get; set; } = true; + public static bool UseSsse3IfAvailable { get; set; } = true; + public static bool UseSse41IfAvailable { get; set; } = true; + public static bool UseSse42IfAvailable { get; set; } = true; + public static bool UsePopCntIfAvailable { get; set; } = true; + public static bool UseAvxIfAvailable { get; set; } = true; + public static bool UseAvx512FIfAvailable { get; set; } = true; + public static bool UseAvx512VlIfAvailable { get; set; } = true; + public static bool UseAvx512BwIfAvailable { get; set; } = true; + public static bool UseAvx512DqIfAvailable { get; set; } = true; + public static bool UseF16cIfAvailable { get; set; } = true; + public static bool UseFmaIfAvailable { get; set; } = true; + public static bool UseAesniIfAvailable { get; set; } = true; + public static bool UsePclmulqdqIfAvailable { get; set; } = true; + public static bool UseShaIfAvailable { get; set; } = true; + public static bool UseGfniIfAvailable { get; set; } = true; + + public static bool ForceLegacySse + { + get => X86HardwareCapabilities.ForceLegacySse; + set => X86HardwareCapabilities.ForceLegacySse = value; + } + + internal static bool UseAdvSimd => UseAdvSimdIfAvailable && Arm64HardwareCapabilities.SupportsAdvSimd; + internal static bool UseArm64Pmull => UseArm64PmullIfAvailable && Arm64HardwareCapabilities.SupportsPmull; + + internal static bool UseSse => UseSseIfAvailable && X86HardwareCapabilities.SupportsSse; + internal static bool UseSse2 => UseSse2IfAvailable && X86HardwareCapabilities.SupportsSse2; + internal static bool UseSse3 => UseSse3IfAvailable && X86HardwareCapabilities.SupportsSse3; + internal static bool UseSsse3 => UseSsse3IfAvailable && X86HardwareCapabilities.SupportsSsse3; + internal static bool UseSse41 => UseSse41IfAvailable && X86HardwareCapabilities.SupportsSse41; + internal static bool UseSse42 => UseSse42IfAvailable && X86HardwareCapabilities.SupportsSse42; + internal static bool UsePopCnt => UsePopCntIfAvailable && X86HardwareCapabilities.SupportsPopcnt; + internal static bool UseAvx => UseAvxIfAvailable && X86HardwareCapabilities.SupportsAvx && !ForceLegacySse; + internal static bool UseAvx512F => UseAvx512FIfAvailable && X86HardwareCapabilities.SupportsAvx512F && !ForceLegacySse; + internal static bool UseAvx512Vl => UseAvx512VlIfAvailable && X86HardwareCapabilities.SupportsAvx512Vl && !ForceLegacySse; + internal static bool UseAvx512Bw => UseAvx512BwIfAvailable && X86HardwareCapabilities.SupportsAvx512Bw && !ForceLegacySse; + internal static bool UseAvx512Dq => UseAvx512DqIfAvailable && X86HardwareCapabilities.SupportsAvx512Dq && !ForceLegacySse; + internal static bool UseF16c => UseF16cIfAvailable && X86HardwareCapabilities.SupportsF16c; + internal static bool UseFma => UseFmaIfAvailable && X86HardwareCapabilities.SupportsFma; + internal static bool UseAesni => UseAesniIfAvailable && X86HardwareCapabilities.SupportsAesni; + internal static bool UsePclmulqdq => UsePclmulqdqIfAvailable && X86HardwareCapabilities.SupportsPclmulqdq; + internal static bool UseSha => UseShaIfAvailable && X86HardwareCapabilities.SupportsSha; + internal static bool UseGfni => UseGfniIfAvailable && X86HardwareCapabilities.SupportsGfni; + + internal static bool UseAvx512Ortho => UseAvx512F && UseAvx512Vl; + internal static bool UseAvx512OrthoFloat => UseAvx512Ortho && UseAvx512Dq; + } +} diff --git a/src/ARMeilleure/Signal/NativeSignalHandler.cs b/src/ARMeilleure/Signal/NativeSignalHandler.cs new file mode 100644 index 00000000..cddeb817 --- /dev/null +++ b/src/ARMeilleure/Signal/NativeSignalHandler.cs @@ -0,0 +1,422 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Memory; +using ARMeilleure.Translation; +using ARMeilleure.Translation.Cache; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Signal +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct SignalHandlerRange + { + public int IsActive; + public nuint RangeAddress; + public nuint RangeEndAddress; + public IntPtr ActionPointer; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct SignalHandlerConfig + { + /// + /// The byte offset of the faulting address in the SigInfo or ExceptionRecord struct. + /// + public int StructAddressOffset; + + /// + /// The byte offset of the write flag in the SigInfo or ExceptionRecord struct. + /// + public int StructWriteOffset; + + /// + /// The sigaction handler that was registered before this one. (unix only) + /// + public nuint UnixOldSigaction; + + /// + /// The type of the previous sigaction. True for the 3 argument variant. (unix only) + /// + public int UnixOldSigaction3Arg; + + public SignalHandlerRange Range0; + public SignalHandlerRange Range1; + public SignalHandlerRange Range2; + public SignalHandlerRange Range3; + public SignalHandlerRange Range4; + public SignalHandlerRange Range5; + public SignalHandlerRange Range6; + public SignalHandlerRange Range7; + } + + public static class NativeSignalHandler + { + private delegate void UnixExceptionHandler(int sig, IntPtr info, IntPtr ucontext); + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + private delegate int VectoredExceptionHandler(IntPtr exceptionInfo); + + private const int MaxTrackedRanges = 8; + + private const int StructAddressOffset = 0; + private const int StructWriteOffset = 4; + private const int UnixOldSigaction = 8; + private const int UnixOldSigaction3Arg = 16; + private const int RangeOffset = 20; + + private const int EXCEPTION_CONTINUE_SEARCH = 0; + private const int EXCEPTION_CONTINUE_EXECUTION = -1; + + private const uint EXCEPTION_ACCESS_VIOLATION = 0xc0000005; + + private static ulong _pageSize; + private static ulong _pageMask; + + private static IntPtr _handlerConfig; + private static IntPtr _signalHandlerPtr; + private static IntPtr _signalHandlerHandle; + + private static readonly object _lock = new object(); + private static bool _initialized; + + static NativeSignalHandler() + { + _handlerConfig = Marshal.AllocHGlobal(Unsafe.SizeOf()); + ref SignalHandlerConfig config = ref GetConfigRef(); + + config = new SignalHandlerConfig(); + } + + public static void Initialize(IJitMemoryAllocator allocator) + { + JitCache.Initialize(allocator); + } + + public static void InitializeSignalHandler(ulong pageSize, Func customSignalHandlerFactory = null) + { + if (_initialized) return; + + lock (_lock) + { + if (_initialized) return; + + _pageSize = pageSize; + _pageMask = pageSize - 1; + + ref SignalHandlerConfig config = ref GetConfigRef(); + + if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + _signalHandlerPtr = Marshal.GetFunctionPointerForDelegate(GenerateUnixSignalHandler(_handlerConfig)); + + if (customSignalHandlerFactory != null) + { + _signalHandlerPtr = customSignalHandlerFactory(UnixSignalHandlerRegistration.GetSegfaultExceptionHandler().sa_handler, _signalHandlerPtr); + } + + var old = UnixSignalHandlerRegistration.RegisterExceptionHandler(_signalHandlerPtr); + + config.UnixOldSigaction = (nuint)(ulong)old.sa_handler; + config.UnixOldSigaction3Arg = old.sa_flags & 4; + } + else + { + config.StructAddressOffset = 40; // ExceptionInformation1 + config.StructWriteOffset = 32; // ExceptionInformation0 + + _signalHandlerPtr = Marshal.GetFunctionPointerForDelegate(GenerateWindowsSignalHandler(_handlerConfig)); + + if (customSignalHandlerFactory != null) + { + _signalHandlerPtr = customSignalHandlerFactory(IntPtr.Zero, _signalHandlerPtr); + } + + _signalHandlerHandle = WindowsSignalHandlerRegistration.RegisterExceptionHandler(_signalHandlerPtr); + } + + _initialized = true; + } + } + + private static unsafe ref SignalHandlerConfig GetConfigRef() + { + return ref Unsafe.AsRef((void*)_handlerConfig); + } + + public static unsafe bool AddTrackedRegion(nuint address, nuint endAddress, IntPtr action) + { + var ranges = &((SignalHandlerConfig*)_handlerConfig)->Range0; + + for (int i = 0; i < MaxTrackedRanges; i++) + { + if (ranges[i].IsActive == 0) + { + ranges[i].RangeAddress = address; + ranges[i].RangeEndAddress = endAddress; + ranges[i].ActionPointer = action; + ranges[i].IsActive = 1; + + return true; + } + } + + return false; + } + + public static unsafe bool RemoveTrackedRegion(nuint address) + { + var ranges = &((SignalHandlerConfig*)_handlerConfig)->Range0; + + for (int i = 0; i < MaxTrackedRanges; i++) + { + if (ranges[i].IsActive == 1 && ranges[i].RangeAddress == address) + { + ranges[i].IsActive = 0; + + return true; + } + } + + return false; + } + + private static Operand EmitGenericRegionCheck(EmitterContext context, IntPtr signalStructPtr, Operand faultAddress, Operand isWrite) + { + Operand inRegionLocal = context.AllocateLocal(OperandType.I32); + context.Copy(inRegionLocal, Const(0)); + + Operand endLabel = Label(); + + for (int i = 0; i < MaxTrackedRanges; i++) + { + ulong rangeBaseOffset = (ulong)(RangeOffset + i * Unsafe.SizeOf()); + + Operand nextLabel = Label(); + + Operand isActive = context.Load(OperandType.I32, Const((ulong)signalStructPtr + rangeBaseOffset)); + + context.BranchIfFalse(nextLabel, isActive); + + Operand rangeAddress = context.Load(OperandType.I64, Const((ulong)signalStructPtr + rangeBaseOffset + 4)); + Operand rangeEndAddress = context.Load(OperandType.I64, Const((ulong)signalStructPtr + rangeBaseOffset + 12)); + + // Is the fault address within this tracked region? + Operand inRange = context.BitwiseAnd( + context.ICompare(faultAddress, rangeAddress, Comparison.GreaterOrEqualUI), + context.ICompare(faultAddress, rangeEndAddress, Comparison.LessUI) + ); + + // Only call tracking if in range. + context.BranchIfFalse(nextLabel, inRange, BasicBlockFrequency.Cold); + + Operand offset = context.BitwiseAnd(context.Subtract(faultAddress, rangeAddress), Const(~_pageMask)); + + // Call the tracking action, with the pointer's relative offset to the base address. + Operand trackingActionPtr = context.Load(OperandType.I64, Const((ulong)signalStructPtr + rangeBaseOffset + 20)); + + context.Copy(inRegionLocal, Const(0)); + + Operand skipActionLabel = Label(); + + // Tracking action should be non-null to call it, otherwise assume false return. + context.BranchIfFalse(skipActionLabel, trackingActionPtr); + Operand result = context.Call(trackingActionPtr, OperandType.I32, offset, Const(_pageSize), isWrite); + context.Copy(inRegionLocal, result); + + context.MarkLabel(skipActionLabel); + + // If the tracking action returns false or does not exist, it might be an invalid access due to a partial overlap on Windows. + if (OperatingSystem.IsWindows()) + { + context.BranchIfTrue(endLabel, inRegionLocal); + + context.Copy(inRegionLocal, WindowsPartialUnmapHandler.EmitRetryFromAccessViolation(context)); + } + + context.Branch(endLabel); + + context.MarkLabel(nextLabel); + } + + context.MarkLabel(endLabel); + + return context.Copy(inRegionLocal); + } + + private static Operand GenerateUnixFaultAddress(EmitterContext context, Operand sigInfoPtr) + { + ulong structAddressOffset = OperatingSystem.IsMacOS() ? 24ul : 16ul; // si_addr + return context.Load(OperandType.I64, context.Add(sigInfoPtr, Const(structAddressOffset))); + } + + private static Operand GenerateUnixWriteFlag(EmitterContext context, Operand ucontextPtr) + { + if (OperatingSystem.IsMacOS()) + { + const ulong mcontextOffset = 48; // uc_mcontext + Operand ctxPtr = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(mcontextOffset))); + + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + const ulong esrOffset = 8; // __es.__esr + Operand esr = context.Load(OperandType.I64, context.Add(ctxPtr, Const(esrOffset))); + return context.BitwiseAnd(esr, Const(0x40ul)); + } + + if (RuntimeInformation.ProcessArchitecture == Architecture.X64) + { + const ulong errOffset = 4; // __es.__err + Operand err = context.Load(OperandType.I64, context.Add(ctxPtr, Const(errOffset))); + return context.BitwiseAnd(err, Const(2ul)); + } + } + else if (OperatingSystem.IsLinux()) + { + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + Operand auxPtr = context.AllocateLocal(OperandType.I64); + + Operand loopLabel = Label(); + Operand successLabel = Label(); + + const ulong auxOffset = 464; // uc_mcontext.__reserved + const uint esrMagic = 0x45535201; + + context.Copy(auxPtr, context.Add(ucontextPtr, Const(auxOffset))); + + context.MarkLabel(loopLabel); + + // _aarch64_ctx::magic + Operand magic = context.Load(OperandType.I32, auxPtr); + // _aarch64_ctx::size + Operand size = context.Load(OperandType.I32, context.Add(auxPtr, Const(4ul))); + + context.BranchIf(successLabel, magic, Const(esrMagic), Comparison.Equal); + + context.Copy(auxPtr, context.Add(auxPtr, context.ZeroExtend32(OperandType.I64, size))); + + context.Branch(loopLabel); + + context.MarkLabel(successLabel); + + // esr_context::esr + Operand esr = context.Load(OperandType.I64, context.Add(auxPtr, Const(8ul))); + return context.BitwiseAnd(esr, Const(0x40ul)); + } + + if (RuntimeInformation.ProcessArchitecture == Architecture.X64) + { + const int errOffset = 192; // uc_mcontext.gregs[REG_ERR] + Operand err = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(errOffset))); + return context.BitwiseAnd(err, Const(2ul)); + } + } + + throw new PlatformNotSupportedException(); + } + + private static UnixExceptionHandler GenerateUnixSignalHandler(IntPtr signalStructPtr) + { + EmitterContext context = new EmitterContext(); + + // (int sig, SigInfo* sigInfo, void* ucontext) + Operand sigInfoPtr = context.LoadArgument(OperandType.I64, 1); + Operand ucontextPtr = context.LoadArgument(OperandType.I64, 2); + + Operand faultAddress = GenerateUnixFaultAddress(context, sigInfoPtr); + Operand writeFlag = GenerateUnixWriteFlag(context, ucontextPtr); + + Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1. + + Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite); + + Operand endLabel = Label(); + + context.BranchIfTrue(endLabel, isInRegion); + + Operand unixOldSigaction = context.Load(OperandType.I64, Const((ulong)signalStructPtr + UnixOldSigaction)); + Operand unixOldSigaction3Arg = context.Load(OperandType.I64, Const((ulong)signalStructPtr + UnixOldSigaction3Arg)); + Operand threeArgLabel = Label(); + + context.BranchIfTrue(threeArgLabel, unixOldSigaction3Arg); + + context.Call(unixOldSigaction, OperandType.None, context.LoadArgument(OperandType.I32, 0)); + context.Branch(endLabel); + + context.MarkLabel(threeArgLabel); + + context.Call(unixOldSigaction, + OperandType.None, + context.LoadArgument(OperandType.I32, 0), + sigInfoPtr, + context.LoadArgument(OperandType.I64, 2) + ); + + context.MarkLabel(endLabel); + + context.Return(); + + ControlFlowGraph cfg = context.GetControlFlowGraph(); + + OperandType[] argTypes = new OperandType[] { OperandType.I32, OperandType.I64, OperandType.I64 }; + + return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map(); + } + + private static VectoredExceptionHandler GenerateWindowsSignalHandler(IntPtr signalStructPtr) + { + EmitterContext context = new EmitterContext(); + + // (ExceptionPointers* exceptionInfo) + Operand exceptionInfoPtr = context.LoadArgument(OperandType.I64, 0); + Operand exceptionRecordPtr = context.Load(OperandType.I64, exceptionInfoPtr); + + // First thing's first - this catches a number of exceptions, but we only want access violations. + Operand validExceptionLabel = Label(); + + Operand exceptionCode = context.Load(OperandType.I32, exceptionRecordPtr); + + context.BranchIf(validExceptionLabel, exceptionCode, Const(EXCEPTION_ACCESS_VIOLATION), Comparison.Equal); + + context.Return(Const(EXCEPTION_CONTINUE_SEARCH)); // Don't handle this one. + + context.MarkLabel(validExceptionLabel); + + // Next, read the address of the invalid access, and whether it is a write or not. + + Operand structAddressOffset = context.Load(OperandType.I32, Const((ulong)signalStructPtr + StructAddressOffset)); + Operand structWriteOffset = context.Load(OperandType.I32, Const((ulong)signalStructPtr + StructWriteOffset)); + + Operand faultAddress = context.Load(OperandType.I64, context.Add(exceptionRecordPtr, context.ZeroExtend32(OperandType.I64, structAddressOffset))); + Operand writeFlag = context.Load(OperandType.I64, context.Add(exceptionRecordPtr, context.ZeroExtend32(OperandType.I64, structWriteOffset))); + + Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1. + + Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite); + + Operand endLabel = Label(); + + // If the region check result is false, then run the next vectored exception handler. + + context.BranchIfTrue(endLabel, isInRegion); + + context.Return(Const(EXCEPTION_CONTINUE_SEARCH)); + + context.MarkLabel(endLabel); + + // Otherwise, return to execution. + + context.Return(Const(EXCEPTION_CONTINUE_EXECUTION)); + + // Compile and return the function. + + ControlFlowGraph cfg = context.GetControlFlowGraph(); + + OperandType[] argTypes = new OperandType[] { OperandType.I64 }; + + return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map(); + } + } +} diff --git a/src/ARMeilleure/Signal/TestMethods.cs b/src/ARMeilleure/Signal/TestMethods.cs new file mode 100644 index 00000000..e2ecad24 --- /dev/null +++ b/src/ARMeilleure/Signal/TestMethods.cs @@ -0,0 +1,84 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using System; +using System.Runtime.InteropServices; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Signal +{ + public struct NativeWriteLoopState + { + public int Running; + public int Error; + } + + public static class TestMethods + { + public delegate bool DebugPartialUnmap(); + public delegate int DebugThreadLocalMapGetOrReserve(int threadId, int initialState); + public delegate void DebugNativeWriteLoop(IntPtr nativeWriteLoopPtr, IntPtr writePtr); + + public static DebugPartialUnmap GenerateDebugPartialUnmap() + { + EmitterContext context = new EmitterContext(); + + var result = WindowsPartialUnmapHandler.EmitRetryFromAccessViolation(context); + + context.Return(result); + + // Compile and return the function. + + ControlFlowGraph cfg = context.GetControlFlowGraph(); + + OperandType[] argTypes = new OperandType[] { OperandType.I64 }; + + return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map(); + } + + public static DebugThreadLocalMapGetOrReserve GenerateDebugThreadLocalMapGetOrReserve(IntPtr structPtr) + { + EmitterContext context = new EmitterContext(); + + var result = WindowsPartialUnmapHandler.EmitThreadLocalMapIntGetOrReserve(context, structPtr, context.LoadArgument(OperandType.I32, 0), context.LoadArgument(OperandType.I32, 1)); + + context.Return(result); + + // Compile and return the function. + + ControlFlowGraph cfg = context.GetControlFlowGraph(); + + OperandType[] argTypes = new OperandType[] { OperandType.I64 }; + + return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map(); + } + + public static DebugNativeWriteLoop GenerateDebugNativeWriteLoop() + { + EmitterContext context = new EmitterContext(); + + // Loop a write to the target address until "running" is false. + + Operand structPtr = context.Copy(context.LoadArgument(OperandType.I64, 0)); + Operand writePtr = context.Copy(context.LoadArgument(OperandType.I64, 1)); + + Operand loopLabel = Label(); + context.MarkLabel(loopLabel); + + context.Store(writePtr, Const(12345)); + + Operand running = context.Load(OperandType.I32, structPtr); + + context.BranchIfTrue(loopLabel, running); + + context.Return(); + + // Compile and return the function. + + ControlFlowGraph cfg = context.GetControlFlowGraph(); + + OperandType[] argTypes = new OperandType[] { OperandType.I64 }; + + return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map(); + } + } +} diff --git a/src/ARMeilleure/Signal/UnixSignalHandlerRegistration.cs b/src/ARMeilleure/Signal/UnixSignalHandlerRegistration.cs new file mode 100644 index 00000000..22009240 --- /dev/null +++ b/src/ARMeilleure/Signal/UnixSignalHandlerRegistration.cs @@ -0,0 +1,83 @@ +using System; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Signal +{ + static partial class UnixSignalHandlerRegistration + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct SigSet + { + fixed long sa_mask[16]; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SigAction + { + public IntPtr sa_handler; + public SigSet sa_mask; + public int sa_flags; + public IntPtr sa_restorer; + } + + private const int SIGSEGV = 11; + private const int SIGBUS = 10; + private const int SA_SIGINFO = 0x00000004; + + [LibraryImport("libc", SetLastError = true)] + private static partial int sigaction(int signum, ref SigAction sigAction, out SigAction oldAction); + + [LibraryImport("libc", SetLastError = true)] + private static partial int sigaction(int signum, IntPtr sigAction, out SigAction oldAction); + + [LibraryImport("libc", SetLastError = true)] + private static partial int sigemptyset(ref SigSet set); + + public static SigAction GetSegfaultExceptionHandler() + { + int result = sigaction(SIGSEGV, IntPtr.Zero, out SigAction old); + + if (result != 0) + { + throw new InvalidOperationException($"Could not get SIGSEGV sigaction. Error: {result}"); + } + + return old; + } + + public static SigAction RegisterExceptionHandler(IntPtr action) + { + SigAction sig = new SigAction + { + sa_handler = action, + sa_flags = SA_SIGINFO + }; + + sigemptyset(ref sig.sa_mask); + + int result = sigaction(SIGSEGV, ref sig, out SigAction old); + + if (result != 0) + { + throw new InvalidOperationException($"Could not register SIGSEGV sigaction. Error: {result}"); + } + + if (OperatingSystem.IsMacOS()) + { + result = sigaction(SIGBUS, ref sig, out _); + + if (result != 0) + { + throw new InvalidOperationException($"Could not register SIGBUS sigaction. Error: {result}"); + } + } + + return old; + } + + public static bool RestoreExceptionHandler(SigAction oldAction) + { + return sigaction(SIGSEGV, ref oldAction, out SigAction _) == 0 && (!OperatingSystem.IsMacOS() || sigaction(SIGBUS, ref oldAction, out SigAction _) == 0); + } + } +} diff --git a/src/ARMeilleure/Signal/WindowsPartialUnmapHandler.cs b/src/ARMeilleure/Signal/WindowsPartialUnmapHandler.cs new file mode 100644 index 00000000..941e36e5 --- /dev/null +++ b/src/ARMeilleure/Signal/WindowsPartialUnmapHandler.cs @@ -0,0 +1,186 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Translation; +using Ryujinx.Common.Memory.PartialUnmaps; +using System; + +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Signal +{ + /// + /// Methods to handle signals caused by partial unmaps. See the structs for C# implementations of the methods. + /// + internal static class WindowsPartialUnmapHandler + { + public static Operand EmitRetryFromAccessViolation(EmitterContext context) + { + IntPtr partialRemapStatePtr = PartialUnmapState.GlobalState; + IntPtr localCountsPtr = IntPtr.Add(partialRemapStatePtr, PartialUnmapState.LocalCountsOffset); + + // Get the lock first. + EmitNativeReaderLockAcquire(context, IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapLockOffset)); + + IntPtr getCurrentThreadId = WindowsSignalHandlerRegistration.GetCurrentThreadIdFunc(); + Operand threadId = context.Call(Const((ulong)getCurrentThreadId), OperandType.I32); + Operand threadIndex = EmitThreadLocalMapIntGetOrReserve(context, localCountsPtr, threadId, Const(0)); + + Operand endLabel = Label(); + Operand retry = context.AllocateLocal(OperandType.I32); + Operand threadIndexValidLabel = Label(); + + context.BranchIfFalse(threadIndexValidLabel, context.ICompareEqual(threadIndex, Const(-1))); + + context.Copy(retry, Const(1)); // Always retry when thread local cannot be allocated. + + context.Branch(endLabel); + + context.MarkLabel(threadIndexValidLabel); + + Operand threadLocalPartialUnmapsPtr = EmitThreadLocalMapIntGetValuePtr(context, localCountsPtr, threadIndex); + Operand threadLocalPartialUnmaps = context.Load(OperandType.I32, threadLocalPartialUnmapsPtr); + Operand partialUnmapsCount = context.Load(OperandType.I32, Const((ulong)IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapsCountOffset))); + + context.Copy(retry, context.ICompareNotEqual(threadLocalPartialUnmaps, partialUnmapsCount)); + + Operand noRetryLabel = Label(); + + context.BranchIfFalse(noRetryLabel, retry); + + // if (retry) { + + context.Store(threadLocalPartialUnmapsPtr, partialUnmapsCount); + + context.Branch(endLabel); + + context.MarkLabel(noRetryLabel); + + // } + + context.MarkLabel(endLabel); + + // Finally, release the lock and return the retry value. + EmitNativeReaderLockRelease(context, IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapLockOffset)); + + return retry; + } + + public static Operand EmitThreadLocalMapIntGetOrReserve(EmitterContext context, IntPtr threadLocalMapPtr, Operand threadId, Operand initialState) + { + Operand idsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap.ThreadIdsOffset)); + + Operand i = context.AllocateLocal(OperandType.I32); + + context.Copy(i, Const(0)); + + // (Loop 1) Check all slots for a matching Thread ID (while also trying to allocate) + + Operand endLabel = Label(); + + Operand loopLabel = Label(); + context.MarkLabel(loopLabel); + + Operand offset = context.Multiply(i, Const(sizeof(int))); + Operand idPtr = context.Add(idsPtr, context.SignExtend32(OperandType.I64, offset)); + + // Check that this slot has the thread ID. + Operand existingId = context.CompareAndSwap(idPtr, threadId, threadId); + + // If it was already the thread ID, then we just need to return i. + context.BranchIfTrue(endLabel, context.ICompareEqual(existingId, threadId)); + + context.Copy(i, context.Add(i, Const(1))); + + context.BranchIfTrue(loopLabel, context.ICompareLess(i, Const(ThreadLocalMap.MapSize))); + + // (Loop 2) Try take a slot that is 0 with our Thread ID. + + context.Copy(i, Const(0)); // Reset i. + + Operand loop2Label = Label(); + context.MarkLabel(loop2Label); + + Operand offset2 = context.Multiply(i, Const(sizeof(int))); + Operand idPtr2 = context.Add(idsPtr, context.SignExtend32(OperandType.I64, offset2)); + + // Try and swap in the thread id on top of 0. + Operand existingId2 = context.CompareAndSwap(idPtr2, Const(0), threadId); + + Operand idNot0Label = Label(); + + // If it was 0, then we need to initialize the struct entry and return i. + context.BranchIfFalse(idNot0Label, context.ICompareEqual(existingId2, Const(0))); + + Operand structsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap.StructsOffset)); + Operand structPtr = context.Add(structsPtr, context.SignExtend32(OperandType.I64, offset2)); + context.Store(structPtr, initialState); + + context.Branch(endLabel); + + context.MarkLabel(idNot0Label); + + context.Copy(i, context.Add(i, Const(1))); + + context.BranchIfTrue(loop2Label, context.ICompareLess(i, Const(ThreadLocalMap.MapSize))); + + context.Copy(i, Const(-1)); // Could not place the thread in the list. + + context.MarkLabel(endLabel); + + return context.Copy(i); + } + + private static Operand EmitThreadLocalMapIntGetValuePtr(EmitterContext context, IntPtr threadLocalMapPtr, Operand index) + { + Operand offset = context.Multiply(index, Const(sizeof(int))); + Operand structsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap.StructsOffset)); + + return context.Add(structsPtr, context.SignExtend32(OperandType.I64, offset)); + } + + private static void EmitThreadLocalMapIntRelease(EmitterContext context, IntPtr threadLocalMapPtr, Operand threadId, Operand index) + { + Operand offset = context.Multiply(index, Const(sizeof(int))); + Operand idsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap.ThreadIdsOffset)); + Operand idPtr = context.Add(idsPtr, context.SignExtend32(OperandType.I64, offset)); + + context.CompareAndSwap(idPtr, threadId, Const(0)); + } + + private static void EmitAtomicAddI32(EmitterContext context, Operand ptr, Operand additive) + { + Operand loop = Label(); + context.MarkLabel(loop); + + Operand initial = context.Load(OperandType.I32, ptr); + Operand newValue = context.Add(initial, additive); + + Operand replaced = context.CompareAndSwap(ptr, initial, newValue); + + context.BranchIfFalse(loop, context.ICompareEqual(initial, replaced)); + } + + private static void EmitNativeReaderLockAcquire(EmitterContext context, IntPtr nativeReaderLockPtr) + { + Operand writeLockPtr = Const((ulong)IntPtr.Add(nativeReaderLockPtr, NativeReaderWriterLock.WriteLockOffset)); + + // Spin until we can acquire the write lock. + Operand spinLabel = Label(); + context.MarkLabel(spinLabel); + + // Old value must be 0 to continue (we gained the write lock) + context.BranchIfTrue(spinLabel, context.CompareAndSwap(writeLockPtr, Const(0), Const(1))); + + // Increment reader count. + EmitAtomicAddI32(context, Const((ulong)IntPtr.Add(nativeReaderLockPtr, NativeReaderWriterLock.ReaderCountOffset)), Const(1)); + + // Release write lock. + context.CompareAndSwap(writeLockPtr, Const(1), Const(0)); + } + + private static void EmitNativeReaderLockRelease(EmitterContext context, IntPtr nativeReaderLockPtr) + { + // Decrement reader count. + EmitAtomicAddI32(context, Const((ulong)IntPtr.Add(nativeReaderLockPtr, NativeReaderWriterLock.ReaderCountOffset)), Const(-1)); + } + } +} diff --git a/src/ARMeilleure/Signal/WindowsSignalHandlerRegistration.cs b/src/ARMeilleure/Signal/WindowsSignalHandlerRegistration.cs new file mode 100644 index 00000000..3219e015 --- /dev/null +++ b/src/ARMeilleure/Signal/WindowsSignalHandlerRegistration.cs @@ -0,0 +1,44 @@ +using System; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Signal +{ + unsafe partial class WindowsSignalHandlerRegistration + { + [LibraryImport("kernel32.dll")] + private static partial IntPtr AddVectoredExceptionHandler(uint first, IntPtr handler); + + [LibraryImport("kernel32.dll")] + private static partial ulong RemoveVectoredExceptionHandler(IntPtr handle); + + [LibraryImport("kernel32.dll", SetLastError = true, EntryPoint = "LoadLibraryA")] + private static partial IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpFileName); + + [LibraryImport("kernel32.dll", SetLastError = true)] + private static partial IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string procName); + + private static IntPtr _getCurrentThreadIdPtr; + + public static IntPtr RegisterExceptionHandler(IntPtr action) + { + return AddVectoredExceptionHandler(1, action); + } + + public static bool RemoveExceptionHandler(IntPtr handle) + { + return RemoveVectoredExceptionHandler(handle) != 0; + } + + public static IntPtr GetCurrentThreadIdFunc() + { + if (_getCurrentThreadIdPtr == IntPtr.Zero) + { + IntPtr handle = LoadLibrary("kernel32.dll"); + + _getCurrentThreadIdPtr = GetProcAddress(handle, "GetCurrentThreadId"); + } + + return _getCurrentThreadIdPtr; + } + } +} diff --git a/src/ARMeilleure/State/Aarch32Mode.cs b/src/ARMeilleure/State/Aarch32Mode.cs new file mode 100644 index 00000000..395e288a --- /dev/null +++ b/src/ARMeilleure/State/Aarch32Mode.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.State +{ + enum Aarch32Mode + { + User = 0b10000, + Fiq = 0b10001, + Irq = 0b10010, + Supervisor = 0b10011, + Monitor = 0b10110, + Abort = 0b10111, + Hypervisor = 0b11010, + Undefined = 0b11011, + System = 0b11111 + } +} \ No newline at end of file diff --git a/src/ARMeilleure/State/ExceptionCallback.cs b/src/ARMeilleure/State/ExceptionCallback.cs new file mode 100644 index 00000000..38d6eef7 --- /dev/null +++ b/src/ARMeilleure/State/ExceptionCallback.cs @@ -0,0 +1,5 @@ +namespace ARMeilleure.State +{ + public delegate void ExceptionCallbackNoArgs(ExecutionContext context); + public delegate void ExceptionCallback(ExecutionContext context, ulong address, int id); +} \ No newline at end of file diff --git a/src/ARMeilleure/State/ExecutionContext.cs b/src/ARMeilleure/State/ExecutionContext.cs new file mode 100644 index 00000000..859fb3a5 --- /dev/null +++ b/src/ARMeilleure/State/ExecutionContext.cs @@ -0,0 +1,173 @@ +using ARMeilleure.Memory; +using System; + +namespace ARMeilleure.State +{ + public class ExecutionContext + { + private const int MinCountForCheck = 4000; + + private NativeContext _nativeContext; + + internal IntPtr NativeContextPtr => _nativeContext.BasePtr; + + private bool _interrupted; + + private readonly ICounter _counter; + + public ulong Pc => _nativeContext.GetPc(); + + public uint CtrEl0 => 0x8444c004; + public uint DczidEl0 => 0x00000004; + + public ulong CntfrqEl0 => _counter.Frequency; + public ulong CntpctEl0 => _counter.Counter; + + // CNTVCT_EL0 = CNTPCT_EL0 - CNTVOFF_EL2 + // Since EL2 isn't implemented, CNTVOFF_EL2 = 0 + public ulong CntvctEl0 => CntpctEl0; + + public long TpidrEl0 + { + get => _nativeContext.GetTpidrEl0(); + set => _nativeContext.SetTpidrEl0(value); + } + + public long TpidrroEl0 + { + get => _nativeContext.GetTpidrroEl0(); + set => _nativeContext.SetTpidrroEl0(value); + } + + public uint Pstate + { + get => _nativeContext.GetPstate(); + set => _nativeContext.SetPstate(value); + } + + public FPSR Fpsr + { + get => (FPSR)_nativeContext.GetFPState((uint)FPSR.Mask); + set => _nativeContext.SetFPState((uint)value, (uint)FPSR.Mask); + } + + public FPCR Fpcr + { + get => (FPCR)_nativeContext.GetFPState((uint)FPCR.Mask); + set => _nativeContext.SetFPState((uint)value, (uint)FPCR.Mask); + } + public FPCR StandardFpcrValue => (Fpcr & (FPCR.Ahp)) | FPCR.Dn | FPCR.Fz; + + public FPSCR Fpscr + { + get => (FPSCR)_nativeContext.GetFPState((uint)FPSCR.Mask); + set => _nativeContext.SetFPState((uint)value, (uint)FPSCR.Mask); + } + + public bool IsAarch32 { get; set; } + + internal ExecutionMode ExecutionMode + { + get + { + if (IsAarch32) + { + return GetPstateFlag(PState.TFlag) + ? ExecutionMode.Aarch32Thumb + : ExecutionMode.Aarch32Arm; + } + else + { + return ExecutionMode.Aarch64; + } + } + } + + public bool Running + { + get => _nativeContext.GetRunning(); + private set => _nativeContext.SetRunning(value); + } + + private readonly ExceptionCallbackNoArgs _interruptCallback; + private readonly ExceptionCallback _breakCallback; + private readonly ExceptionCallback _supervisorCallback; + private readonly ExceptionCallback _undefinedCallback; + + public ExecutionContext( + IJitMemoryAllocator allocator, + ICounter counter, + ExceptionCallbackNoArgs interruptCallback = null, + ExceptionCallback breakCallback = null, + ExceptionCallback supervisorCallback = null, + ExceptionCallback undefinedCallback = null) + { + _nativeContext = new NativeContext(allocator); + _counter = counter; + _interruptCallback = interruptCallback; + _breakCallback = breakCallback; + _supervisorCallback = supervisorCallback; + _undefinedCallback = undefinedCallback; + + Running = true; + + _nativeContext.SetCounter(MinCountForCheck); + } + + public ulong GetX(int index) => _nativeContext.GetX(index); + public void SetX(int index, ulong value) => _nativeContext.SetX(index, value); + + public V128 GetV(int index) => _nativeContext.GetV(index); + public void SetV(int index, V128 value) => _nativeContext.SetV(index, value); + + public bool GetPstateFlag(PState flag) => _nativeContext.GetPstateFlag(flag); + public void SetPstateFlag(PState flag, bool value) => _nativeContext.SetPstateFlag(flag, value); + + public bool GetFPstateFlag(FPState flag) => _nativeContext.GetFPStateFlag(flag); + public void SetFPstateFlag(FPState flag, bool value) => _nativeContext.SetFPStateFlag(flag, value); + + internal void CheckInterrupt() + { + if (_interrupted) + { + _interrupted = false; + + _interruptCallback?.Invoke(this); + } + + _nativeContext.SetCounter(MinCountForCheck); + } + + public void RequestInterrupt() + { + _interrupted = true; + } + + internal void OnBreak(ulong address, int imm) + { + _breakCallback?.Invoke(this, address, imm); + } + + internal void OnSupervisorCall(ulong address, int imm) + { + _supervisorCallback?.Invoke(this, address, imm); + } + + internal void OnUndefined(ulong address, int opCode) + { + _undefinedCallback?.Invoke(this, address, opCode); + } + + public void StopRunning() + { + Running = false; + + _nativeContext.SetCounter(0); + } + + public void Dispose() + { + _nativeContext.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/State/ExecutionMode.cs b/src/ARMeilleure/State/ExecutionMode.cs new file mode 100644 index 00000000..29154a25 --- /dev/null +++ b/src/ARMeilleure/State/ExecutionMode.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.State +{ + enum ExecutionMode : int + { + Aarch32Arm = 0, + Aarch32Thumb = 1, + Aarch64 = 2 + } +} \ No newline at end of file diff --git a/src/ARMeilleure/State/FPCR.cs b/src/ARMeilleure/State/FPCR.cs new file mode 100644 index 00000000..6f707de7 --- /dev/null +++ b/src/ARMeilleure/State/FPCR.cs @@ -0,0 +1,22 @@ +using System; + +namespace ARMeilleure.State +{ + [Flags] + public enum FPCR : uint + { + Ioe = 1u << 8, + Dze = 1u << 9, + Ofe = 1u << 10, + Ufe = 1u << 11, + Ixe = 1u << 12, + Ide = 1u << 15, + RMode0 = 1u << 22, + RMode1 = 1u << 23, + Fz = 1u << 24, + Dn = 1u << 25, + Ahp = 1u << 26, + + Mask = Ahp | Dn | Fz | RMode1 | RMode0 | Ide | Ixe | Ufe | Ofe | Dze | Ioe // 0x07C09F00u + } +} diff --git a/src/ARMeilleure/State/FPException.cs b/src/ARMeilleure/State/FPException.cs new file mode 100644 index 00000000..e24e07af --- /dev/null +++ b/src/ARMeilleure/State/FPException.cs @@ -0,0 +1,12 @@ +namespace ARMeilleure.State +{ + enum FPException + { + InvalidOp = 0, + DivideByZero = 1, + Overflow = 2, + Underflow = 3, + Inexact = 4, + InputDenorm = 7 + } +} diff --git a/src/ARMeilleure/State/FPRoundingMode.cs b/src/ARMeilleure/State/FPRoundingMode.cs new file mode 100644 index 00000000..8d757a15 --- /dev/null +++ b/src/ARMeilleure/State/FPRoundingMode.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.State +{ + public enum FPRoundingMode + { + ToNearest = 0, // With ties to even. + TowardsPlusInfinity = 1, + TowardsMinusInfinity = 2, + TowardsZero = 3, + ToNearestAway = 4 // With ties to away. + } +} diff --git a/src/ARMeilleure/State/FPSCR.cs b/src/ARMeilleure/State/FPSCR.cs new file mode 100644 index 00000000..d6d2fc26 --- /dev/null +++ b/src/ARMeilleure/State/FPSCR.cs @@ -0,0 +1,15 @@ +using System; + +namespace ARMeilleure.State +{ + [Flags] + public enum FPSCR : uint + { + V = 1u << 28, + C = 1u << 29, + Z = 1u << 30, + N = 1u << 31, + + Mask = N | Z | C | V | FPSR.Mask | FPCR.Mask // 0xFFC09F9Fu + } +} diff --git a/src/ARMeilleure/State/FPSR.cs b/src/ARMeilleure/State/FPSR.cs new file mode 100644 index 00000000..5e66d5ce --- /dev/null +++ b/src/ARMeilleure/State/FPSR.cs @@ -0,0 +1,18 @@ +using System; + +namespace ARMeilleure.State +{ + [Flags] + public enum FPSR : uint + { + Ioc = 1u << 0, + Dzc = 1u << 1, + Ofc = 1u << 2, + Ufc = 1u << 3, + Ixc = 1u << 4, + Idc = 1u << 7, + Qc = 1u << 27, + + Mask = Qc | Idc | Ixc | Ufc | Ofc | Dzc | Ioc // 0x0800009Fu + } +} diff --git a/src/ARMeilleure/State/FPState.cs b/src/ARMeilleure/State/FPState.cs new file mode 100644 index 00000000..fa6ab9d4 --- /dev/null +++ b/src/ARMeilleure/State/FPState.cs @@ -0,0 +1,31 @@ +namespace ARMeilleure.State +{ + public enum FPState + { + // FPSR Flags. + IocFlag = 0, + DzcFlag = 1, + OfcFlag = 2, + UfcFlag = 3, + IxcFlag = 4, + IdcFlag = 7, + QcFlag = 27, + VFlag = 28, + CFlag = 29, + ZFlag = 30, + NFlag = 31, + + // FPCR Flags. + IoeFlag = 8, + DzeFlag = 9, + OfeFlag = 10, + UfeFlag = 11, + IxeFlag = 12, + IdeFlag = 15, + RMode0Flag = 22, + RMode1Flag = 23, + FzFlag = 24, + DnFlag = 25, + AhpFlag = 26 + } +} diff --git a/src/ARMeilleure/State/FPType.cs b/src/ARMeilleure/State/FPType.cs new file mode 100644 index 00000000..84e0db8d --- /dev/null +++ b/src/ARMeilleure/State/FPType.cs @@ -0,0 +1,11 @@ +namespace ARMeilleure.State +{ + enum FPType + { + Nonzero, + Zero, + Infinity, + QNaN, + SNaN + } +} diff --git a/src/ARMeilleure/State/ICounter.cs b/src/ARMeilleure/State/ICounter.cs new file mode 100644 index 00000000..93e721ea --- /dev/null +++ b/src/ARMeilleure/State/ICounter.cs @@ -0,0 +1,18 @@ +namespace ARMeilleure.State +{ + /// + /// CPU Counter interface. + /// + public interface ICounter + { + /// + /// Counter frequency in Hertz. + /// + ulong Frequency { get; } + + /// + /// Current counter value. + /// + ulong Counter { get; } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/State/NativeContext.cs b/src/ARMeilleure/State/NativeContext.cs new file mode 100644 index 00000000..3189bdd8 --- /dev/null +++ b/src/ARMeilleure/State/NativeContext.cs @@ -0,0 +1,269 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Memory; +using System; +using System.Runtime.CompilerServices; + +namespace ARMeilleure.State +{ + class NativeContext : IDisposable + { + private unsafe struct NativeCtxStorage + { + public fixed ulong X[RegisterConsts.IntRegsCount]; + public fixed ulong V[RegisterConsts.VecRegsCount * 2]; + public fixed uint Flags[RegisterConsts.FlagsCount]; + public fixed uint FpFlags[RegisterConsts.FpFlagsCount]; + public long TpidrEl0; + public long TpidrroEl0; + public int Counter; + public ulong DispatchAddress; + public ulong ExclusiveAddress; + public ulong ExclusiveValueLow; + public ulong ExclusiveValueHigh; + public int Running; + } + + private static NativeCtxStorage _dummyStorage = new NativeCtxStorage(); + + private readonly IJitMemoryBlock _block; + + public IntPtr BasePtr => _block.Pointer; + + public NativeContext(IJitMemoryAllocator allocator) + { + _block = allocator.Allocate((ulong)Unsafe.SizeOf()); + + GetStorage().ExclusiveAddress = ulong.MaxValue; + } + + public ulong GetPc() + { + // TODO: More precise tracking of PC value. + return GetStorage().DispatchAddress; + } + + public unsafe ulong GetX(int index) + { + if ((uint)index >= RegisterConsts.IntRegsCount) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return GetStorage().X[index]; + } + + public unsafe void SetX(int index, ulong value) + { + if ((uint)index >= RegisterConsts.IntRegsCount) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + GetStorage().X[index] = value; + } + + public unsafe V128 GetV(int index) + { + if ((uint)index >= RegisterConsts.VecRegsCount) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return new V128(GetStorage().V[index * 2 + 0], GetStorage().V[index * 2 + 1]); + } + + public unsafe void SetV(int index, V128 value) + { + if ((uint)index >= RegisterConsts.VecRegsCount) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + GetStorage().V[index * 2 + 0] = value.Extract(0); + GetStorage().V[index * 2 + 1] = value.Extract(1); + } + + public unsafe bool GetPstateFlag(PState flag) + { + if ((uint)flag >= RegisterConsts.FlagsCount) + { + throw new ArgumentException($"Invalid flag \"{flag}\" specified."); + } + + return GetStorage().Flags[(int)flag] != 0; + } + + public unsafe void SetPstateFlag(PState flag, bool value) + { + if ((uint)flag >= RegisterConsts.FlagsCount) + { + throw new ArgumentException($"Invalid flag \"{flag}\" specified."); + } + + GetStorage().Flags[(int)flag] = value ? 1u : 0u; + } + + public unsafe uint GetPstate() + { + uint value = 0; + for (int flag = 0; flag < RegisterConsts.FlagsCount; flag++) + { + value |= GetStorage().Flags[flag] != 0 ? 1u << flag : 0u; + } + return value; + } + + public unsafe void SetPstate(uint value) + { + for (int flag = 0; flag < RegisterConsts.FlagsCount; flag++) + { + uint bit = 1u << flag; + GetStorage().Flags[flag] = (value & bit) == bit ? 1u : 0u; + } + } + + public unsafe bool GetFPStateFlag(FPState flag) + { + if ((uint)flag >= RegisterConsts.FpFlagsCount) + { + throw new ArgumentException($"Invalid flag \"{flag}\" specified."); + } + + return GetStorage().FpFlags[(int)flag] != 0; + } + + public unsafe void SetFPStateFlag(FPState flag, bool value) + { + if ((uint)flag >= RegisterConsts.FpFlagsCount) + { + throw new ArgumentException($"Invalid flag \"{flag}\" specified."); + } + + GetStorage().FpFlags[(int)flag] = value ? 1u : 0u; + } + + public unsafe uint GetFPState(uint mask = uint.MaxValue) + { + uint value = 0; + for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++) + { + uint bit = 1u << flag; + + if ((mask & bit) == bit) + { + value |= GetStorage().FpFlags[flag] != 0 ? bit : 0u; + } + } + return value; + } + + public unsafe void SetFPState(uint value, uint mask = uint.MaxValue) + { + for (int flag = 0; flag < RegisterConsts.FpFlagsCount; flag++) + { + uint bit = 1u << flag; + + if ((mask & bit) == bit) + { + GetStorage().FpFlags[flag] = (value & bit) == bit ? 1u : 0u; + } + } + } + + public long GetTpidrEl0() => GetStorage().TpidrEl0; + public void SetTpidrEl0(long value) => GetStorage().TpidrEl0 = value; + + public long GetTpidrroEl0() => GetStorage().TpidrroEl0; + public void SetTpidrroEl0(long value) => GetStorage().TpidrroEl0 = value; + + public int GetCounter() => GetStorage().Counter; + public void SetCounter(int value) => GetStorage().Counter = value; + + public bool GetRunning() => GetStorage().Running != 0; + public void SetRunning(bool value) => GetStorage().Running = value ? 1 : 0; + + public unsafe static int GetRegisterOffset(Register reg) + { + if (reg.Type == RegisterType.Integer) + { + if ((uint)reg.Index >= RegisterConsts.IntRegsCount) + { + throw new ArgumentException("Invalid register."); + } + + return StorageOffset(ref _dummyStorage, ref _dummyStorage.X[reg.Index]); + } + else if (reg.Type == RegisterType.Vector) + { + if ((uint)reg.Index >= RegisterConsts.VecRegsCount) + { + throw new ArgumentException("Invalid register."); + } + + return StorageOffset(ref _dummyStorage, ref _dummyStorage.V[reg.Index * 2]); + } + else if (reg.Type == RegisterType.Flag) + { + if ((uint)reg.Index >= RegisterConsts.FlagsCount) + { + throw new ArgumentException("Invalid register."); + } + + return StorageOffset(ref _dummyStorage, ref _dummyStorage.Flags[reg.Index]); + } + else /* if (reg.Type == RegisterType.FpFlag) */ + { + if ((uint)reg.Index >= RegisterConsts.FpFlagsCount) + { + throw new ArgumentException("Invalid register."); + } + + return StorageOffset(ref _dummyStorage, ref _dummyStorage.FpFlags[reg.Index]); + } + } + + public static int GetTpidrEl0Offset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.TpidrEl0); + } + + public static int GetTpidrroEl0Offset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.TpidrroEl0); + } + + public static int GetCounterOffset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.Counter); + } + + public static int GetDispatchAddressOffset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.DispatchAddress); + } + + public static int GetExclusiveAddressOffset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.ExclusiveAddress); + } + + public static int GetExclusiveValueOffset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.ExclusiveValueLow); + } + + public static int GetRunningOffset() + { + return StorageOffset(ref _dummyStorage, ref _dummyStorage.Running); + } + + private static int StorageOffset(ref NativeCtxStorage storage, ref T target) + { + return (int)Unsafe.ByteOffset(ref Unsafe.As(ref storage), ref target); + } + + private unsafe ref NativeCtxStorage GetStorage() => ref Unsafe.AsRef((void*)_block.Pointer); + + public void Dispose() => _block.Dispose(); + } +} \ No newline at end of file diff --git a/src/ARMeilleure/State/PState.cs b/src/ARMeilleure/State/PState.cs new file mode 100644 index 00000000..9a80bc57 --- /dev/null +++ b/src/ARMeilleure/State/PState.cs @@ -0,0 +1,17 @@ +namespace ARMeilleure.State +{ + public enum PState + { + TFlag = 5, + EFlag = 9, + GE0Flag = 16, + GE1Flag = 17, + GE2Flag = 18, + GE3Flag = 19, + QFlag = 27, + VFlag = 28, + CFlag = 29, + ZFlag = 30, + NFlag = 31 + } +} diff --git a/src/ARMeilleure/State/RegisterAlias.cs b/src/ARMeilleure/State/RegisterAlias.cs new file mode 100644 index 00000000..7ebfa275 --- /dev/null +++ b/src/ARMeilleure/State/RegisterAlias.cs @@ -0,0 +1,42 @@ +namespace ARMeilleure.State +{ + static class RegisterAlias + { + public const int R8Usr = 8; + public const int R9Usr = 9; + public const int R10Usr = 10; + public const int R11Usr = 11; + public const int R12Usr = 12; + public const int SpUsr = 13; + public const int LrUsr = 14; + + public const int SpHyp = 15; + + public const int LrIrq = 16; + public const int SpIrq = 17; + + public const int LrSvc = 18; + public const int SpSvc = 19; + + public const int LrAbt = 20; + public const int SpAbt = 21; + + public const int LrUnd = 22; + public const int SpUnd = 23; + + public const int R8Fiq = 24; + public const int R9Fiq = 25; + public const int R10Fiq = 26; + public const int R11Fiq = 27; + public const int R12Fiq = 28; + public const int SpFiq = 29; + public const int LrFiq = 30; + + public const int Aarch32Sp = 13; + public const int Aarch32Lr = 14; + public const int Aarch32Pc = 15; + + public const int Lr = 30; + public const int Zr = 31; + } +} \ No newline at end of file diff --git a/src/ARMeilleure/State/RegisterConsts.cs b/src/ARMeilleure/State/RegisterConsts.cs new file mode 100644 index 00000000..d6294080 --- /dev/null +++ b/src/ARMeilleure/State/RegisterConsts.cs @@ -0,0 +1,15 @@ +namespace ARMeilleure.State +{ + static class RegisterConsts + { + public const int IntRegsCount = 32; + public const int VecRegsCount = 32; + public const int FlagsCount = 32; + public const int FpFlagsCount = 32; + public const int IntAndVecRegsCount = IntRegsCount + VecRegsCount; + public const int FpFlagsOffset = IntRegsCount + VecRegsCount + FlagsCount; + public const int TotalCount = IntRegsCount + VecRegsCount + FlagsCount + FpFlagsCount; + + public const int ZeroIndex = 31; + } +} \ No newline at end of file diff --git a/src/ARMeilleure/State/V128.cs b/src/ARMeilleure/State/V128.cs new file mode 100644 index 00000000..3fa9f9a9 --- /dev/null +++ b/src/ARMeilleure/State/V128.cs @@ -0,0 +1,312 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace ARMeilleure.State +{ + /// + /// Represents a 128-bit vector. + /// + [StructLayout(LayoutKind.Sequential, Size = 16)] + public struct V128 : IEquatable + { + // _e0 & _e1 could be marked as readonly, however they are not readonly because we modify them through the Unsafe + // APIs. This also means that one should be careful when changing the layout of this struct. + + private ulong _e0; + private ulong _e1; + + /// + /// Gets a new with all bits set to zero. + /// + public static V128 Zero => new V128(0, 0); + + /// + /// Initializes a new instance of the struct with the specified value + /// as a scalar. + /// + /// Scalar value + public V128(double value) : this(value, 0) { } + + /// + /// Initializes a new instance of the struct with the specified elements. + /// + /// Element 0 + /// Element 1 + public V128(double e0, double e1) + { + _e0 = (ulong)BitConverter.DoubleToInt64Bits(e0); + _e1 = (ulong)BitConverter.DoubleToInt64Bits(e1); + } + + /// + /// Initializes a new instance of the struct with the specified value as a + /// scalar. + /// + /// Scalar value + public V128(float value) : this(value, 0, 0, 0) { } + + /// + /// Initializes a new instance of the struct with the specified elements. + /// + /// Element 0 + /// Element 1 + /// Element 2 + /// Element 3 + public V128(float e0, float e1, float e2, float e3) + { + _e0 = (ulong)(uint)BitConverter.SingleToInt32Bits(e0) << 0; + _e0 |= (ulong)(uint)BitConverter.SingleToInt32Bits(e1) << 32; + _e1 = (ulong)(uint)BitConverter.SingleToInt32Bits(e2) << 0; + _e1 |= (ulong)(uint)BitConverter.SingleToInt32Bits(e3) << 32; + } + + /// + /// Initializes a new instance of the struct with the specified + /// elements. + /// + /// Element 0 + /// Element 1 + public V128(long e0, long e1) : this((ulong)e0, (ulong)e1) { } + + /// + /// Initializes a new instance of the struct with the specified elements. + /// + /// Element 0 + /// Element 1 + public V128(ulong e0, ulong e1) + { + _e0 = e0; + _e1 = e1; + } + + /// + /// Initializes a new instance of the struct with the specified elements. + /// + /// Element 0 + /// Element 1 + /// Element 2 + /// Element 3 + public V128(int e0, int e1, int e2, int e3) : this((uint)e0, (uint)e1, (uint)e2, (uint)e3) { } + + /// + /// Initializes a new instance of the struct with the specified elements. + /// + /// Element 0 + /// Element 1 + /// Element 2 + /// Element 3 + public V128(uint e0, uint e1, uint e2, uint e3) + { + _e0 = (ulong)e0 << 0; + _e0 |= (ulong)e1 << 32; + _e1 = (ulong)e2 << 0; + _e1 |= (ulong)e3 << 32; + } + + /// + /// Initializes a new instance of the struct from the specified array. + /// + /// array to use + public V128(byte[] data) + { + _e0 = (ulong)BitConverter.ToInt64(data, 0); + _e1 = (ulong)BitConverter.ToInt64(data, 8); + } + + /// + /// Returns the value of the as a scalar. + /// + /// Type of scalar + /// Value of the as a scalar + /// Size of is larger than 16 bytes + public T As() where T : unmanaged + { + return Extract(0); + } + + /// + /// Extracts the element at the specified index as a from the . + /// + /// Element type + /// Index of element + /// Element at the specified index as a from the + /// + /// is out of bound or the size of is larger than 16 bytes + /// + public T Extract(int index) where T : unmanaged + { + if ((uint)index >= GetElementCount()) + ThrowIndexOutOfRange(); + + // Performs: + // return *((*T)this + index); + return Unsafe.Add(ref Unsafe.As(ref this), index); + } + + /// + /// Inserts the specified value into the element at the specified index in the . + /// + /// Element type + /// Index of element + /// Value to insert + /// + /// is out of bound or the size of is larger than 16 bytes + /// + public void Insert(int index, T value) where T : unmanaged + { + if ((uint)index >= GetElementCount()) + ThrowIndexOutOfRange(); + + // Performs: + // *((*T)this + index) = value; + Unsafe.Add(ref Unsafe.As(ref this), index) = value; + } + + /// + /// Returns a new array which represents the . + /// + /// A new array which represents the + public byte[] ToArray() + { + byte[] data = new byte[16]; + Span span = data; + + BitConverter.TryWriteBytes(span, _e0); + BitConverter.TryWriteBytes(span.Slice(8), _e1); + + return data; + } + + /// + /// Performs a bitwise logical left shift on the specified by the specified shift count. + /// + /// instance + /// Number of shifts + /// Result of left shift + /// + /// This supports shift counts up to 63; anything above may result in unexpected behaviour. + /// + public static V128 operator <<(V128 x, int shift) + { + if (shift == 0) + { + return new V128(x._e0, x._e1); + } + + ulong shiftOut = x._e0 >> (64 - shift); + + return new V128(x._e0 << shift, (x._e1 << shift) | shiftOut); + } + + /// + /// Performs a bitwise logical right shift on the specified by the specified shift count. + /// + /// instance + /// Number of shifts + /// Result of right shift + /// + /// This supports shift counts up to 63; anything above may result in unexpected behaviour. + /// + public static V128 operator >>(V128 x, int shift) + { + if (shift == 0) + { + return new V128(x._e0, x._e1); + } + + ulong shiftOut = x._e1 & ((1UL << shift) - 1); + + return new V128((x._e0 >> shift) | (shiftOut << (64 - shift)), x._e1 >> shift); + } + + /// + /// Performs a bitwise not on the specified . + /// + /// Target + /// Result of not operation + public static V128 operator ~(V128 x) => new V128(~x._e0, ~x._e1); + + /// + /// Performs a bitwise and on the specified instances. + /// + /// First instance + /// Second instance + /// Result of and operation + public static V128 operator &(V128 x, V128 y) => new V128(x._e0 & y._e0, x._e1 & y._e1); + + /// + /// Performs a bitwise or on the specified instances. + /// + /// First instance + /// Second instance + /// Result of or operation + public static V128 operator |(V128 x, V128 y) => new V128(x._e0 | y._e0, x._e1 | y._e1); + + /// + /// Performs a bitwise exlusive or on the specified instances. + /// + /// First instance + /// Second instance + /// Result of exclusive or operation + public static V128 operator ^(V128 x, V128 y) => new V128(x._e0 ^ y._e0, x._e1 ^ y._e1); + + /// + /// Determines if the specified instances are equal. + /// + /// First instance + /// Second instance + /// true if equal; otherwise false + public static bool operator ==(V128 x, V128 y) => x.Equals(y); + + /// + /// Determines if the specified instances are not equal. + /// + /// First instance + /// Second instance + /// true if not equal; otherwise false + public static bool operator !=(V128 x, V128 y) => !x.Equals(y); + + /// + /// Determines if the specified is equal to this instance. + /// + /// Other instance + /// true if equal; otherwise false + public bool Equals(V128 other) + { + return other._e0 == _e0 && other._e1 == _e1; + } + + /// + /// Determines if the specified is equal to this instance. + /// + /// Other instance + /// true if equal; otherwise false + public override bool Equals(object obj) + { + return obj is V128 vector && Equals(vector); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(_e0, _e1); + } + + /// + public override string ToString() + { + return $"0x{_e1:X16}{_e0:X16}"; + } + + private uint GetElementCount() where T : unmanaged + { + return (uint)(Unsafe.SizeOf() / Unsafe.SizeOf()); + } + + private static void ThrowIndexOutOfRange() + { + throw new ArgumentOutOfRangeException("index"); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Statistics.cs b/src/ARMeilleure/Statistics.cs new file mode 100644 index 00000000..fbc64708 --- /dev/null +++ b/src/ARMeilleure/Statistics.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; + +namespace ARMeilleure +{ + public static class Statistics + { + private const int ReportMaxFunctions = 100; + +#pragma warning disable CS0169 + [ThreadStatic] + private static Stopwatch _executionTimer; +#pragma warning restore CS0169 + + private static ConcurrentDictionary _ticksPerFunction; + + static Statistics() + { + _ticksPerFunction = new ConcurrentDictionary(); + } + + public static void InitializeTimer() + { +#if M_PROFILE + if (_executionTimer == null) + { + _executionTimer = new Stopwatch(); + } +#endif + } + + internal static void StartTimer() + { +#if M_PROFILE + _executionTimer.Restart(); +#endif + } + + internal static void StopTimer(ulong funcAddr) + { +#if M_PROFILE + _executionTimer.Stop(); + + long ticks = _executionTimer.ElapsedTicks; + + _ticksPerFunction.AddOrUpdate(funcAddr, ticks, (key, oldTicks) => oldTicks + ticks); +#endif + } + + internal static void ResumeTimer() + { +#if M_PROFILE + _executionTimer.Start(); +#endif + } + + internal static void PauseTimer() + { +#if M_PROFILE + _executionTimer.Stop(); +#endif + } + + public static string GetReport() + { + int count = 0; + + StringBuilder sb = new StringBuilder(); + + sb.AppendLine(" Function address | Time"); + sb.AppendLine("--------------------------"); + + KeyValuePair[] funcTable = _ticksPerFunction.ToArray(); + + foreach (KeyValuePair kv in funcTable.OrderByDescending(x => x.Value)) + { + long timeInMs = (kv.Value * 1000) / Stopwatch.Frequency; + + sb.AppendLine($" 0x{kv.Key:X16} | {timeInMs} ms"); + + if (count++ >= ReportMaxFunctions) + { + break; + } + } + + return sb.ToString(); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/ArmEmitterContext.cs b/src/ARMeilleure/Translation/ArmEmitterContext.cs new file mode 100644 index 00000000..565d2aad --- /dev/null +++ b/src/ARMeilleure/Translation/ArmEmitterContext.cs @@ -0,0 +1,282 @@ +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.Common; +using ARMeilleure.Decoders; +using ARMeilleure.Diagnostics; +using ARMeilleure.Instructions; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Memory; +using ARMeilleure.State; +using System; +using System.Collections.Generic; +using System.Reflection; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Translation +{ + class ArmEmitterContext : EmitterContext + { + private readonly Dictionary _labels; + + private OpCode _optOpLastCompare; + private OpCode _optOpLastFlagSet; + + private Operand _optCmpTempN; + private Operand _optCmpTempM; + + private Block _currBlock; + + public Block CurrBlock + { + get + { + return _currBlock; + } + set + { + _currBlock = value; + + ResetBlockState(); + } + } + + private bool _pendingQcFlagSync; + + public OpCode CurrOp { get; set; } + + public IMemoryManager Memory { get; } + + public EntryTable CountTable { get; } + public AddressTable FunctionTable { get; } + public TranslatorStubs Stubs { get; } + + public ulong EntryAddress { get; } + public bool HighCq { get; } + public bool HasPtc { get; } + public Aarch32Mode Mode { get; } + + private int _ifThenBlockStateIndex = 0; + private Condition[] _ifThenBlockState = { }; + public bool IsInIfThenBlock => _ifThenBlockStateIndex < _ifThenBlockState.Length; + public Condition CurrentIfThenBlockCond => _ifThenBlockState[_ifThenBlockStateIndex]; + + public ArmEmitterContext( + IMemoryManager memory, + EntryTable countTable, + AddressTable funcTable, + TranslatorStubs stubs, + ulong entryAddress, + bool highCq, + bool hasPtc, + Aarch32Mode mode) + { + Memory = memory; + CountTable = countTable; + FunctionTable = funcTable; + Stubs = stubs; + EntryAddress = entryAddress; + HighCq = highCq; + HasPtc = hasPtc; + Mode = mode; + + _labels = new Dictionary(); + } + + public override Operand Call(MethodInfo info, params Operand[] callArgs) + { + SyncQcFlag(); + + if (!HasPtc) + { + return base.Call(info, callArgs); + } + else + { + int index = Delegates.GetDelegateIndex(info); + IntPtr funcPtr = Delegates.GetDelegateFuncPtrByIndex(index); + + OperandType returnType = GetOperandType(info.ReturnType); + + Symbol symbol = new Symbol(SymbolType.DelegateTable, (ulong)index); + + Symbols.Add((ulong)funcPtr.ToInt64(), info.Name); + + return Call(Const(funcPtr.ToInt64(), symbol), returnType, callArgs); + } + } + + public Operand GetLabel(ulong address) + { + if (!_labels.TryGetValue(address, out Operand label)) + { + label = Label(); + + _labels.Add(address, label); + } + + return label; + } + + public void MarkComparison(Operand n, Operand m) + { + _optOpLastCompare = CurrOp; + + _optCmpTempN = Copy(n); + _optCmpTempM = Copy(m); + } + + public void MarkFlagSet(PState stateFlag) + { + // Set this only if any of the NZCV flag bits were modified. + // This is used to ensure that when emiting a direct IL branch + // instruction for compare + branch sequences, we're not expecting + // to use comparison values from an old instruction, when in fact + // the flags were already overwritten by another instruction further along. + if (stateFlag >= PState.VFlag) + { + _optOpLastFlagSet = CurrOp; + } + } + + private void ResetBlockState() + { + _optOpLastCompare = null; + _optOpLastFlagSet = null; + } + + public void SetPendingQcFlagSync() + { + _pendingQcFlagSync = true; + } + + public void SyncQcFlag() + { + if (_pendingQcFlagSync) + { + if (Optimizations.UseAdvSimd) + { + Operand fpsr = AddIntrinsicInt(Intrinsic.Arm64MrsFpsr); + + uint qcFlagMask = (uint)FPSR.Qc; + + Operand qcClearLabel = Label(); + + BranchIfFalse(qcClearLabel, BitwiseAnd(fpsr, Const(qcFlagMask))); + + AddIntrinsicNoRet(Intrinsic.Arm64MsrFpsr, Const(0)); + InstEmitHelper.SetFpFlag(this, FPState.QcFlag, Const(1)); + + MarkLabel(qcClearLabel); + } + + _pendingQcFlagSync = false; + } + } + + public void ClearQcFlag() + { + if (Optimizations.UseAdvSimd) + { + AddIntrinsicNoRet(Intrinsic.Arm64MsrFpsr, Const(0)); + } + } + + public void ClearQcFlagIfModified() + { + if (_pendingQcFlagSync && Optimizations.UseAdvSimd) + { + AddIntrinsicNoRet(Intrinsic.Arm64MsrFpsr, Const(0)); + } + } + + public void EnterArmFpMode() + { + InstEmitSimdHelper.EnterArmFpMode(this, InstEmitHelper.GetFpFlag); + } + + public void UpdateArmFpMode() + { + EnterArmFpMode(); + } + + public void ExitArmFpMode() + { + InstEmitSimdHelper.ExitArmFpMode(this, (flag, value) => InstEmitHelper.SetFpFlag(this, flag, value)); + } + + public Operand TryGetComparisonResult(Condition condition) + { + if (_optOpLastCompare == null || _optOpLastCompare != _optOpLastFlagSet) + { + return default; + } + + Operand n = _optCmpTempN; + Operand m = _optCmpTempM; + + InstName cmpName = _optOpLastCompare.Instruction.Name; + + if (cmpName == InstName.Subs) + { + switch (condition) + { + case Condition.Eq: return ICompareEqual (n, m); + case Condition.Ne: return ICompareNotEqual (n, m); + case Condition.GeUn: return ICompareGreaterOrEqualUI(n, m); + case Condition.LtUn: return ICompareLessUI (n, m); + case Condition.GtUn: return ICompareGreaterUI (n, m); + case Condition.LeUn: return ICompareLessOrEqualUI (n, m); + case Condition.Ge: return ICompareGreaterOrEqual (n, m); + case Condition.Lt: return ICompareLess (n, m); + case Condition.Gt: return ICompareGreater (n, m); + case Condition.Le: return ICompareLessOrEqual (n, m); + } + } + else if (cmpName == InstName.Adds && _optOpLastCompare is IOpCodeAluImm op) + { + // There are several limitations that needs to be taken into account for CMN comparisons: + // - The unsigned comparisons are not valid, as they depend on the + // carry flag value, and they will have different values for addition and + // subtraction. For addition, it's carry, and for subtraction, it's borrow. + // So, we need to make sure we're not doing a unsigned compare for the CMN case. + // - We can only do the optimization for the immediate variants, + // because when the second operand value is exactly INT_MIN, we can't + // negate the value as theres no positive counterpart. + // Such invalid values can't be encoded on the immediate encodings. + if (op.RegisterSize == RegisterSize.Int32) + { + m = Const((int)-op.Immediate); + } + else + { + m = Const(-op.Immediate); + } + + switch (condition) + { + case Condition.Eq: return ICompareEqual (n, m); + case Condition.Ne: return ICompareNotEqual (n, m); + case Condition.Ge: return ICompareGreaterOrEqual(n, m); + case Condition.Lt: return ICompareLess (n, m); + case Condition.Gt: return ICompareGreater (n, m); + case Condition.Le: return ICompareLessOrEqual (n, m); + } + } + + return default; + } + + public void SetIfThenBlockState(Condition[] state) + { + _ifThenBlockState = state; + _ifThenBlockStateIndex = 0; + } + + public void AdvanceIfThenBlockState() + { + if (IsInIfThenBlock) + { + _ifThenBlockStateIndex++; + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/Cache/CacheEntry.cs b/src/ARMeilleure/Translation/Cache/CacheEntry.cs new file mode 100644 index 00000000..dc5503b1 --- /dev/null +++ b/src/ARMeilleure/Translation/Cache/CacheEntry.cs @@ -0,0 +1,26 @@ +using ARMeilleure.CodeGen.Unwinding; +using System; +using System.Diagnostics.CodeAnalysis; + +namespace ARMeilleure.Translation.Cache +{ + readonly struct CacheEntry : IComparable + { + public int Offset { get; } + public int Size { get; } + + public UnwindInfo UnwindInfo { get; } + + public CacheEntry(int offset, int size, UnwindInfo unwindInfo) + { + Offset = offset; + Size = size; + UnwindInfo = unwindInfo; + } + + public int CompareTo([AllowNull] CacheEntry other) + { + return Offset.CompareTo(other.Offset); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/Cache/CacheMemoryAllocator.cs b/src/ARMeilleure/Translation/Cache/CacheMemoryAllocator.cs new file mode 100644 index 00000000..4c22de40 --- /dev/null +++ b/src/ARMeilleure/Translation/Cache/CacheMemoryAllocator.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace ARMeilleure.Translation.Cache +{ + class CacheMemoryAllocator + { + private readonly struct MemoryBlock : IComparable + { + public int Offset { get; } + public int Size { get; } + + public MemoryBlock(int offset, int size) + { + Offset = offset; + Size = size; + } + + public int CompareTo([AllowNull] MemoryBlock other) + { + return Offset.CompareTo(other.Offset); + } + } + + private readonly List _blocks = new List(); + + public CacheMemoryAllocator(int capacity) + { + _blocks.Add(new MemoryBlock(0, capacity)); + } + + public int Allocate(int size) + { + for (int i = 0; i < _blocks.Count; i++) + { + MemoryBlock block = _blocks[i]; + + if (block.Size > size) + { + _blocks[i] = new MemoryBlock(block.Offset + size, block.Size - size); + return block.Offset; + } + else if (block.Size == size) + { + _blocks.RemoveAt(i); + return block.Offset; + } + } + + // We don't have enough free memory to perform the allocation. + return -1; + } + + public void Free(int offset, int size) + { + Insert(new MemoryBlock(offset, size)); + } + + private void Insert(MemoryBlock block) + { + int index = _blocks.BinarySearch(block); + + if (index < 0) + { + index = ~index; + } + + if (index < _blocks.Count) + { + MemoryBlock next = _blocks[index]; + + int endOffs = block.Offset + block.Size; + + if (next.Offset == endOffs) + { + block = new MemoryBlock(block.Offset, block.Size + next.Size); + _blocks.RemoveAt(index); + } + } + + if (index > 0) + { + MemoryBlock prev = _blocks[index - 1]; + + if (prev.Offset + prev.Size == block.Offset) + { + block = new MemoryBlock(block.Offset - prev.Size, block.Size + prev.Size); + _blocks.RemoveAt(--index); + } + } + + _blocks.Insert(index, block); + } + } +} diff --git a/src/ARMeilleure/Translation/Cache/JitCache.cs b/src/ARMeilleure/Translation/Cache/JitCache.cs new file mode 100644 index 00000000..f496a8e9 --- /dev/null +++ b/src/ARMeilleure/Translation/Cache/JitCache.cs @@ -0,0 +1,198 @@ +using ARMeilleure.CodeGen; +using ARMeilleure.CodeGen.Unwinding; +using ARMeilleure.Memory; +using ARMeilleure.Native; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Translation.Cache +{ + static class JitCache + { + private const int PageSize = 4 * 1024; + private const int PageMask = PageSize - 1; + + private const int CodeAlignment = 4; // Bytes. + private const int CacheSize = 2047 * 1024 * 1024; + + private static ReservedRegion _jitRegion; + private static JitCacheInvalidation _jitCacheInvalidator; + + private static CacheMemoryAllocator _cacheAllocator; + + private static readonly List _cacheEntries = new List(); + + private static readonly object _lock = new object(); + private static bool _initialized; + + public static void Initialize(IJitMemoryAllocator allocator) + { + if (_initialized) return; + + lock (_lock) + { + if (_initialized) return; + + _jitRegion = new ReservedRegion(allocator, CacheSize); + _jitCacheInvalidator = new JitCacheInvalidation(allocator); + + _cacheAllocator = new CacheMemoryAllocator(CacheSize); + + if (OperatingSystem.IsWindows()) + { + JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, CacheSize, _jitRegion.Pointer + Allocate(PageSize)); + } + + _initialized = true; + } + } + + public static IntPtr Map(CompiledFunction func) + { + byte[] code = func.Code; + + lock (_lock) + { + Debug.Assert(_initialized); + + int funcOffset = Allocate(code.Length); + + IntPtr funcPtr = _jitRegion.Pointer + funcOffset; + + if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + unsafe + { + fixed (byte *codePtr = code) + { + JitSupportDarwin.Copy(funcPtr, (IntPtr)codePtr, (ulong)code.Length); + } + } + } + else + { + ReprotectAsWritable(funcOffset, code.Length); + Marshal.Copy(code, 0, funcPtr, code.Length); + ReprotectAsExecutable(funcOffset, code.Length); + + _jitCacheInvalidator.Invalidate(funcPtr, (ulong)code.Length); + } + + Add(funcOffset, code.Length, func.UnwindInfo); + + return funcPtr; + } + } + + public static void Unmap(IntPtr pointer) + { + lock (_lock) + { + Debug.Assert(_initialized); + + int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64()); + + bool result = TryFind(funcOffset, out CacheEntry entry); + Debug.Assert(result); + + _cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size)); + + Remove(funcOffset); + } + } + + private static void ReprotectAsWritable(int offset, int size) + { + int endOffs = offset + size; + + int regionStart = offset & ~PageMask; + int regionEnd = (endOffs + PageMask) & ~PageMask; + + _jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart)); + } + + private static void ReprotectAsExecutable(int offset, int size) + { + int endOffs = offset + size; + + int regionStart = offset & ~PageMask; + int regionEnd = (endOffs + PageMask) & ~PageMask; + + _jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart)); + } + + private static int Allocate(int codeSize) + { + codeSize = AlignCodeSize(codeSize); + + int allocOffset = _cacheAllocator.Allocate(codeSize); + + if (allocOffset < 0) + { + throw new OutOfMemoryException("JIT Cache exhausted."); + } + + _jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize); + + return allocOffset; + } + + private static int AlignCodeSize(int codeSize) + { + return checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1); + } + + private static void Add(int offset, int size, UnwindInfo unwindInfo) + { + CacheEntry entry = new CacheEntry(offset, size, unwindInfo); + + int index = _cacheEntries.BinarySearch(entry); + + if (index < 0) + { + index = ~index; + } + + _cacheEntries.Insert(index, entry); + } + + private static void Remove(int offset) + { + int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default)); + + if (index < 0) + { + index = ~index - 1; + } + + if (index >= 0) + { + _cacheEntries.RemoveAt(index); + } + } + + public static bool TryFind(int offset, out CacheEntry entry) + { + lock (_lock) + { + int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default)); + + if (index < 0) + { + index = ~index - 1; + } + + if (index >= 0) + { + entry = _cacheEntries[index]; + return true; + } + } + + entry = default; + return false; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/Cache/JitCacheInvalidation.cs b/src/ARMeilleure/Translation/Cache/JitCacheInvalidation.cs new file mode 100644 index 00000000..ec2ae73b --- /dev/null +++ b/src/ARMeilleure/Translation/Cache/JitCacheInvalidation.cs @@ -0,0 +1,79 @@ +using ARMeilleure.Memory; +using System; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Translation.Cache +{ + class JitCacheInvalidation + { + private static int[] _invalidationCode = new int[] + { + unchecked((int)0xd53b0022), // mrs x2, ctr_el0 + unchecked((int)0xd3504c44), // ubfx x4, x2, #16, #4 + unchecked((int)0x52800083), // mov w3, #0x4 + unchecked((int)0x12000c45), // and w5, w2, #0xf + unchecked((int)0x1ac42064), // lsl w4, w3, w4 + unchecked((int)0x51000482), // sub w2, w4, #0x1 + unchecked((int)0x8a220002), // bic x2, x0, x2 + unchecked((int)0x1ac52063), // lsl w3, w3, w5 + unchecked((int)0xeb01005f), // cmp x2, x1 + unchecked((int)0x93407c84), // sxtw x4, w4 + unchecked((int)0x540000a2), // b.cs 3c + unchecked((int)0xd50b7b22), // dc cvau, x2 + unchecked((int)0x8b040042), // add x2, x2, x4 + unchecked((int)0xeb02003f), // cmp x1, x2 + unchecked((int)0x54ffffa8), // b.hi 2c + unchecked((int)0xd5033b9f), // dsb ish + unchecked((int)0x51000462), // sub w2, w3, #0x1 + unchecked((int)0x93407c63), // sxtw x3, w3 + unchecked((int)0x8a220000), // bic x0, x0, x2 + unchecked((int)0xeb00003f), // cmp x1, x0 + unchecked((int)0x540000a9), // b.ls 64 + unchecked((int)0xd50b7520), // ic ivau, x0 + unchecked((int)0x8b030000), // add x0, x0, x3 + unchecked((int)0xeb00003f), // cmp x1, x0 + unchecked((int)0x54ffffa8), // b.hi 54 + unchecked((int)0xd5033b9f), // dsb ish + unchecked((int)0xd5033fdf), // isb + unchecked((int)0xd65f03c0), // ret + }; + + private delegate void InvalidateCache(ulong start, ulong end); + + private InvalidateCache _invalidateCache; + private ReservedRegion _invalidateCacheCodeRegion; + + private readonly bool _needsInvalidation; + + public JitCacheInvalidation(IJitMemoryAllocator allocator) + { + // On macOS, a different path is used to write to the JIT cache, which does the invalidation. + if (!OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + ulong size = (ulong)_invalidationCode.Length * sizeof(int); + ulong mask = (ulong)ReservedRegion.DefaultGranularity - 1; + + size = (size + mask) & ~mask; + + _invalidateCacheCodeRegion = new ReservedRegion(allocator, size); + _invalidateCacheCodeRegion.ExpandIfNeeded(size); + + Marshal.Copy(_invalidationCode, 0, _invalidateCacheCodeRegion.Pointer, _invalidationCode.Length); + + _invalidateCacheCodeRegion.Block.MapAsRx(0, size); + + _invalidateCache = Marshal.GetDelegateForFunctionPointer(_invalidateCacheCodeRegion.Pointer); + + _needsInvalidation = true; + } + } + + public void Invalidate(IntPtr basePointer, ulong size) + { + if (_needsInvalidation) + { + _invalidateCache((ulong)basePointer, (ulong)basePointer + size); + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/Cache/JitUnwindWindows.cs b/src/ARMeilleure/Translation/Cache/JitUnwindWindows.cs new file mode 100644 index 00000000..77727bf1 --- /dev/null +++ b/src/ARMeilleure/Translation/Cache/JitUnwindWindows.cs @@ -0,0 +1,189 @@ +// https://github.com/MicrosoftDocs/cpp-docs/blob/master/docs/build/exception-handling-x64.md + +using ARMeilleure.CodeGen.Unwinding; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Translation.Cache +{ + static partial class JitUnwindWindows + { + private const int MaxUnwindCodesArraySize = 32; // Must be an even value. + + private struct RuntimeFunction + { + public uint BeginAddress; + public uint EndAddress; + public uint UnwindData; + } + + private struct UnwindInfo + { + public byte VersionAndFlags; + public byte SizeOfProlog; + public byte CountOfUnwindCodes; + public byte FrameRegister; + public unsafe fixed ushort UnwindCodes[MaxUnwindCodesArraySize]; + } + + private enum UnwindOp + { + PushNonvol = 0, + AllocLarge = 1, + AllocSmall = 2, + SetFpreg = 3, + SaveNonvol = 4, + SaveNonvolFar = 5, + SaveXmm128 = 8, + SaveXmm128Far = 9, + PushMachframe = 10 + } + + private unsafe delegate RuntimeFunction* GetRuntimeFunctionCallback(ulong controlPc, IntPtr context); + + [LibraryImport("kernel32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static unsafe partial bool RtlInstallFunctionTableCallback( + ulong tableIdentifier, + ulong baseAddress, + uint length, + GetRuntimeFunctionCallback callback, + IntPtr context, + [MarshalAs(UnmanagedType.LPWStr)] string outOfProcessCallbackDll); + + private static GetRuntimeFunctionCallback _getRuntimeFunctionCallback; + + private static int _sizeOfRuntimeFunction; + + private unsafe static RuntimeFunction* _runtimeFunction; + + private unsafe static UnwindInfo* _unwindInfo; + + public static void InstallFunctionTableHandler(IntPtr codeCachePointer, uint codeCacheLength, IntPtr workBufferPtr) + { + ulong codeCachePtr = (ulong)codeCachePointer.ToInt64(); + + _sizeOfRuntimeFunction = Marshal.SizeOf(); + + bool result; + + unsafe + { + _runtimeFunction = (RuntimeFunction*)workBufferPtr; + + _unwindInfo = (UnwindInfo*)(workBufferPtr + _sizeOfRuntimeFunction); + + _getRuntimeFunctionCallback = new GetRuntimeFunctionCallback(FunctionTableHandler); + + result = RtlInstallFunctionTableCallback( + codeCachePtr | 3, + codeCachePtr, + codeCacheLength, + _getRuntimeFunctionCallback, + codeCachePointer, + null); + } + + if (!result) + { + throw new InvalidOperationException("Failure installing function table callback."); + } + } + + private static unsafe RuntimeFunction* FunctionTableHandler(ulong controlPc, IntPtr context) + { + int offset = (int)((long)controlPc - context.ToInt64()); + + if (!JitCache.TryFind(offset, out CacheEntry funcEntry)) + { + return null; // Not found. + } + + var unwindInfo = funcEntry.UnwindInfo; + + int codeIndex = 0; + + for (int index = unwindInfo.PushEntries.Length - 1; index >= 0; index--) + { + var entry = unwindInfo.PushEntries[index]; + + switch (entry.PseudoOp) + { + case UnwindPseudoOp.SaveXmm128: + { + int stackOffset = entry.StackOffsetOrAllocSize; + + Debug.Assert(stackOffset % 16 == 0); + + if (stackOffset <= 0xFFFF0) + { + _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128, entry.PrologOffset, entry.RegIndex); + _unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset / 16); + } + else + { + _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.SaveXmm128Far, entry.PrologOffset, entry.RegIndex); + _unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 0); + _unwindInfo->UnwindCodes[codeIndex++] = (ushort)(stackOffset >> 16); + } + + break; + } + + case UnwindPseudoOp.AllocStack: + { + int allocSize = entry.StackOffsetOrAllocSize; + + Debug.Assert(allocSize % 8 == 0); + + if (allocSize <= 128) + { + _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocSmall, entry.PrologOffset, (allocSize / 8) - 1); + } + else if (allocSize <= 0x7FFF8) + { + _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 0); + _unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize / 8); + } + else + { + _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.AllocLarge, entry.PrologOffset, 1); + _unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 0); + _unwindInfo->UnwindCodes[codeIndex++] = (ushort)(allocSize >> 16); + } + + break; + } + + case UnwindPseudoOp.PushReg: + { + _unwindInfo->UnwindCodes[codeIndex++] = PackUnwindOp(UnwindOp.PushNonvol, entry.PrologOffset, entry.RegIndex); + + break; + } + + default: throw new NotImplementedException($"({nameof(entry.PseudoOp)} = {entry.PseudoOp})"); + } + } + + Debug.Assert(codeIndex <= MaxUnwindCodesArraySize); + + _unwindInfo->VersionAndFlags = 1; // Flags: The function has no handler. + _unwindInfo->SizeOfProlog = (byte)unwindInfo.PrologSize; + _unwindInfo->CountOfUnwindCodes = (byte)codeIndex; + _unwindInfo->FrameRegister = 0; + + _runtimeFunction->BeginAddress = (uint)funcEntry.Offset; + _runtimeFunction->EndAddress = (uint)(funcEntry.Offset + funcEntry.Size); + _runtimeFunction->UnwindData = (uint)_sizeOfRuntimeFunction; + + return _runtimeFunction; + } + + private static ushort PackUnwindOp(UnwindOp op, int prologOffset, int opInfo) + { + return (ushort)(prologOffset | ((int)op << 8) | (opInfo << 12)); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/Compiler.cs b/src/ARMeilleure/Translation/Compiler.cs new file mode 100644 index 00000000..d4aa5cd9 --- /dev/null +++ b/src/ARMeilleure/Translation/Compiler.cs @@ -0,0 +1,68 @@ +using ARMeilleure.CodeGen; +using ARMeilleure.CodeGen.Optimizations; +using ARMeilleure.Diagnostics; +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Translation +{ + static class Compiler + { + public static CompiledFunction Compile( + ControlFlowGraph cfg, + OperandType[] argTypes, + OperandType retType, + CompilerOptions options, + Architecture target) + { + CompilerContext cctx = new(cfg, argTypes, retType, options); + + if (options.HasFlag(CompilerOptions.Optimize)) + { + Logger.StartPass(PassName.TailMerge); + + TailMerge.RunPass(cctx); + + Logger.EndPass(PassName.TailMerge, cfg); + } + + if (options.HasFlag(CompilerOptions.SsaForm)) + { + Logger.StartPass(PassName.Dominance); + + Dominance.FindDominators(cfg); + Dominance.FindDominanceFrontiers(cfg); + + Logger.EndPass(PassName.Dominance); + + Logger.StartPass(PassName.SsaConstruction); + + Ssa.Construct(cfg); + + Logger.EndPass(PassName.SsaConstruction, cfg); + } + else + { + Logger.StartPass(PassName.RegisterToLocal); + + RegisterToLocal.Rename(cfg); + + Logger.EndPass(PassName.RegisterToLocal, cfg); + } + + if (target == Architecture.X64) + { + return CodeGen.X86.CodeGenerator.Generate(cctx); + } + else if (target == Architecture.Arm64) + { + return CodeGen.Arm64.CodeGenerator.Generate(cctx); + } + else + { + throw new NotImplementedException(target.ToString()); + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/CompilerContext.cs b/src/ARMeilleure/Translation/CompilerContext.cs new file mode 100644 index 00000000..510dec58 --- /dev/null +++ b/src/ARMeilleure/Translation/CompilerContext.cs @@ -0,0 +1,26 @@ +using ARMeilleure.IntermediateRepresentation; + +namespace ARMeilleure.Translation +{ + readonly struct CompilerContext + { + public ControlFlowGraph Cfg { get; } + + public OperandType[] FuncArgTypes { get; } + public OperandType FuncReturnType { get; } + + public CompilerOptions Options { get; } + + public CompilerContext( + ControlFlowGraph cfg, + OperandType[] funcArgTypes, + OperandType funcReturnType, + CompilerOptions options) + { + Cfg = cfg; + FuncArgTypes = funcArgTypes; + FuncReturnType = funcReturnType; + Options = options; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/CompilerOptions.cs b/src/ARMeilleure/Translation/CompilerOptions.cs new file mode 100644 index 00000000..0a07ed4a --- /dev/null +++ b/src/ARMeilleure/Translation/CompilerOptions.cs @@ -0,0 +1,17 @@ +using System; + +namespace ARMeilleure.Translation +{ + [Flags] + enum CompilerOptions + { + None = 0, + SsaForm = 1 << 0, + Optimize = 1 << 1, + Lsra = 1 << 2, + Relocatable = 1 << 3, + + MediumCq = SsaForm | Optimize, + HighCq = SsaForm | Optimize | Lsra + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/ControlFlowGraph.cs b/src/ARMeilleure/Translation/ControlFlowGraph.cs new file mode 100644 index 00000000..c935f152 --- /dev/null +++ b/src/ARMeilleure/Translation/ControlFlowGraph.cs @@ -0,0 +1,155 @@ +using ARMeilleure.IntermediateRepresentation; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace ARMeilleure.Translation +{ + class ControlFlowGraph + { + private BasicBlock[] _postOrderBlocks; + private int[] _postOrderMap; + + public int LocalsCount { get; private set; } + public BasicBlock Entry { get; } + public IntrusiveList Blocks { get; } + public BasicBlock[] PostOrderBlocks => _postOrderBlocks; + public int[] PostOrderMap => _postOrderMap; + + public ControlFlowGraph(BasicBlock entry, IntrusiveList blocks, int localsCount) + { + Entry = entry; + Blocks = blocks; + LocalsCount = localsCount; + + Update(); + } + + public Operand AllocateLocal(OperandType type) + { + Operand result = Operand.Factory.Local(type); + + result.NumberLocal(++LocalsCount); + + return result; + } + + public void Update() + { + RemoveUnreachableBlocks(Blocks); + + var visited = new HashSet(); + var blockStack = new Stack(); + + Array.Resize(ref _postOrderBlocks, Blocks.Count); + Array.Resize(ref _postOrderMap, Blocks.Count); + + visited.Add(Entry); + blockStack.Push(Entry); + + int index = 0; + + while (blockStack.TryPop(out BasicBlock block)) + { + bool visitedNew = false; + + for (int i = 0; i < block.SuccessorsCount; i++) + { + BasicBlock succ = block.GetSuccessor(i); + + if (visited.Add(succ)) + { + blockStack.Push(block); + blockStack.Push(succ); + + visitedNew = true; + + break; + } + } + + if (!visitedNew) + { + PostOrderMap[block.Index] = index; + + PostOrderBlocks[index++] = block; + } + } + } + + private void RemoveUnreachableBlocks(IntrusiveList blocks) + { + var visited = new HashSet(); + var workQueue = new Queue(); + + visited.Add(Entry); + workQueue.Enqueue(Entry); + + while (workQueue.TryDequeue(out BasicBlock block)) + { + Debug.Assert(block.Index != -1, "Invalid block index."); + + for (int i = 0; i < block.SuccessorsCount; i++) + { + BasicBlock succ = block.GetSuccessor(i); + + if (visited.Add(succ)) + { + workQueue.Enqueue(succ); + } + } + } + + if (visited.Count < blocks.Count) + { + // Remove unreachable blocks and renumber. + int index = 0; + + for (BasicBlock block = blocks.First; block != null;) + { + BasicBlock nextBlock = block.ListNext; + + if (!visited.Contains(block)) + { + while (block.SuccessorsCount > 0) + { + block.RemoveSuccessor(index: block.SuccessorsCount - 1); + } + + blocks.Remove(block); + } + else + { + block.Index = index++; + } + + block = nextBlock; + } + } + } + + public BasicBlock SplitEdge(BasicBlock predecessor, BasicBlock successor) + { + BasicBlock splitBlock = new BasicBlock(Blocks.Count); + + for (int i = 0; i < predecessor.SuccessorsCount; i++) + { + if (predecessor.GetSuccessor(i) == successor) + { + predecessor.SetSuccessor(i, splitBlock); + } + } + + if (splitBlock.Predecessors.Count == 0) + { + throw new ArgumentException("Predecessor and successor are not connected."); + } + + splitBlock.AddSuccessor(successor); + + Blocks.AddBefore(successor, splitBlock); + + return splitBlock; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/DelegateHelper.cs b/src/ARMeilleure/Translation/DelegateHelper.cs new file mode 100644 index 00000000..43a39bab --- /dev/null +++ b/src/ARMeilleure/Translation/DelegateHelper.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; + +namespace ARMeilleure.Translation +{ + static class DelegateHelper + { + private const string DelegateTypesAssemblyName = "JitDelegateTypes"; + + private static readonly ModuleBuilder _modBuilder; + + private static readonly Dictionary _delegateTypesCache; + + static DelegateHelper() + { + AssemblyBuilder asmBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(DelegateTypesAssemblyName), AssemblyBuilderAccess.Run); + + _modBuilder = asmBuilder.DefineDynamicModule(DelegateTypesAssemblyName); + + _delegateTypesCache = new Dictionary(); + } + + public static Delegate GetDelegate(MethodInfo info) + { + ArgumentNullException.ThrowIfNull(info); + + Type[] parameters = info.GetParameters().Select(pI => pI.ParameterType).ToArray(); + Type returnType = info.ReturnType; + + Type delegateType = GetDelegateType(parameters, returnType); + + return Delegate.CreateDelegate(delegateType, info); + } + + private static Type GetDelegateType(Type[] parameters, Type returnType) + { + string key = GetFunctionSignatureKey(parameters, returnType); + + if (!_delegateTypesCache.TryGetValue(key, out Type delegateType)) + { + delegateType = MakeDelegateType(parameters, returnType, key); + + _delegateTypesCache.TryAdd(key, delegateType); + } + + return delegateType; + } + + private static string GetFunctionSignatureKey(Type[] parameters, Type returnType) + { + string sig = GetTypeName(returnType); + + foreach (Type type in parameters) + { + sig += '_' + GetTypeName(type); + } + + return sig; + } + + private static string GetTypeName(Type type) + { + return type.FullName.Replace(".", string.Empty); + } + + private const MethodAttributes CtorAttributes = + MethodAttributes.RTSpecialName | + MethodAttributes.HideBySig | + MethodAttributes.Public; + + private const TypeAttributes DelegateTypeAttributes = + TypeAttributes.Class | + TypeAttributes.Public | + TypeAttributes.Sealed | + TypeAttributes.AnsiClass | + TypeAttributes.AutoClass; + + private const MethodImplAttributes ImplAttributes = + MethodImplAttributes.Runtime | + MethodImplAttributes.Managed; + + private const MethodAttributes InvokeAttributes = + MethodAttributes.Public | + MethodAttributes.HideBySig | + MethodAttributes.NewSlot | + MethodAttributes.Virtual; + + private static readonly Type[] _delegateCtorSignature = { typeof(object), typeof(IntPtr) }; + + private static Type MakeDelegateType(Type[] parameters, Type returnType, string name) + { + TypeBuilder builder = _modBuilder.DefineType(name, DelegateTypeAttributes, typeof(MulticastDelegate)); + + builder.DefineConstructor(CtorAttributes, CallingConventions.Standard, _delegateCtorSignature).SetImplementationFlags(ImplAttributes); + + builder.DefineMethod("Invoke", InvokeAttributes, returnType, parameters).SetImplementationFlags(ImplAttributes); + + return builder.CreateTypeInfo(); + } + } +} diff --git a/src/ARMeilleure/Translation/DelegateInfo.cs b/src/ARMeilleure/Translation/DelegateInfo.cs new file mode 100644 index 00000000..36320ac3 --- /dev/null +++ b/src/ARMeilleure/Translation/DelegateInfo.cs @@ -0,0 +1,19 @@ +using System; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Translation +{ + class DelegateInfo + { + private readonly Delegate _dlg; // Ensure that this delegate will not be garbage collected. + + public IntPtr FuncPtr { get; } + + public DelegateInfo(Delegate dlg) + { + _dlg = dlg; + + FuncPtr = Marshal.GetFunctionPointerForDelegate(dlg); + } + } +} diff --git a/src/ARMeilleure/Translation/Delegates.cs b/src/ARMeilleure/Translation/Delegates.cs new file mode 100644 index 00000000..55f1e514 --- /dev/null +++ b/src/ARMeilleure/Translation/Delegates.cs @@ -0,0 +1,261 @@ +using ARMeilleure.Instructions; +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace ARMeilleure.Translation +{ + static class Delegates + { + public static bool TryGetDelegateFuncPtrByIndex(int index, out IntPtr funcPtr) + { + if (index >= 0 && index < _delegates.Count) + { + funcPtr = _delegates.Values[index].FuncPtr; // O(1). + + return true; + } + else + { + funcPtr = default; + + return false; + } + } + + public static IntPtr GetDelegateFuncPtrByIndex(int index) + { + if (index < 0 || index >= _delegates.Count) + { + throw new ArgumentOutOfRangeException($"({nameof(index)} = {index})"); + } + + return _delegates.Values[index].FuncPtr; // O(1). + } + + public static IntPtr GetDelegateFuncPtr(MethodInfo info) + { + ArgumentNullException.ThrowIfNull(info); + + string key = GetKey(info); + + if (!_delegates.TryGetValue(key, out DelegateInfo dlgInfo)) // O(log(n)). + { + throw new KeyNotFoundException($"({nameof(key)} = {key})"); + } + + return dlgInfo.FuncPtr; + } + + public static int GetDelegateIndex(MethodInfo info) + { + ArgumentNullException.ThrowIfNull(info); + + string key = GetKey(info); + + int index = _delegates.IndexOfKey(key); // O(log(n)). + + if (index == -1) + { + throw new KeyNotFoundException($"({nameof(key)} = {key})"); + } + + return index; + } + + private static void SetDelegateInfo(MethodInfo info) + { + string key = GetKey(info); + + Delegate dlg = DelegateHelper.GetDelegate(info); + + _delegates.Add(key, new DelegateInfo(dlg)); // ArgumentException (key). + } + + private static string GetKey(MethodInfo info) + { + return $"{info.DeclaringType.Name}.{info.Name}"; + } + + private static readonly SortedList _delegates; + + static Delegates() + { + _delegates = new SortedList(); + + SetDelegateInfo(typeof(Math).GetMethod(nameof(Math.Abs), new Type[] { typeof(double) })); + SetDelegateInfo(typeof(Math).GetMethod(nameof(Math.Ceiling), new Type[] { typeof(double) })); + SetDelegateInfo(typeof(Math).GetMethod(nameof(Math.Floor), new Type[] { typeof(double) })); + SetDelegateInfo(typeof(Math).GetMethod(nameof(Math.Round), new Type[] { typeof(double), typeof(MidpointRounding) })); + SetDelegateInfo(typeof(Math).GetMethod(nameof(Math.Truncate), new Type[] { typeof(double) })); + + SetDelegateInfo(typeof(MathF).GetMethod(nameof(MathF.Abs), new Type[] { typeof(float) })); + SetDelegateInfo(typeof(MathF).GetMethod(nameof(MathF.Ceiling), new Type[] { typeof(float) })); + SetDelegateInfo(typeof(MathF).GetMethod(nameof(MathF.Floor), new Type[] { typeof(float) })); + SetDelegateInfo(typeof(MathF).GetMethod(nameof(MathF.Round), new Type[] { typeof(float), typeof(MidpointRounding) })); + SetDelegateInfo(typeof(MathF).GetMethod(nameof(MathF.Truncate), new Type[] { typeof(float) })); + + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.Break))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.CheckSynchronization))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.EnqueueForRejit))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntfrqEl0))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntpctEl0))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntvctEl0))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCtrEl0))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetDczidEl0))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.InvalidateCacheLine))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadByte))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt16))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt32))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadUInt64))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ReadVector128))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SignalMemoryTracking))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.SupervisorCall))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.ThrowInvalidMemoryAccess))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.Undefined))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteByte))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt16))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt32))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteUInt64))); + SetDelegateInfo(typeof(NativeInterface).GetMethod(nameof(NativeInterface.WriteVector128))); + + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.CountLeadingSigns))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.CountLeadingZeros))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32b))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32cb))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32ch))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32cw))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32cx))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32h))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32w))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Crc32x))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Decrypt))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Encrypt))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.FixedRotate))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashChoose))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashLower))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashMajority))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashParity))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.HashUpper))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.InverseMixColumns))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.MixColumns))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.PolynomialMult64_128))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS32))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToS64))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU32))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF32ToU64))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToS32))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToS64))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToU32))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SatF64ToU64))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha1SchedulePart1))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha1SchedulePart2))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha256SchedulePart1))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Sha256SchedulePart2))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.SignedShrImm64))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl1))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl2))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl3))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbl4))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx1))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx2))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx3))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.Tbx4))); + SetDelegateInfo(typeof(SoftFallback).GetMethod(nameof(SoftFallback.UnsignedShrImm64))); + + SetDelegateInfo(typeof(SoftFloat16_32).GetMethod(nameof(SoftFloat16_32.FPConvert))); + SetDelegateInfo(typeof(SoftFloat16_64).GetMethod(nameof(SoftFloat16_64.FPConvert))); + + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPAdd))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPAddFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompare))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareEQ))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareEQFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareGE))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareGEFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareGT))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareGTFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareLE))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareLEFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareLT))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPCompareLTFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPDiv))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMax))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMaxFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMaxNum))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMaxNumFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMin))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMinFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMinNum))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMinNumFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMul))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMulFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMulAdd))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMulAddFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMulSub))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMulSubFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPMulX))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPNegMulAdd))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPNegMulSub))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRecipEstimate))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRecipEstimateFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRecipStep))); // A32 only. + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRecipStepFused))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRecpX))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRSqrtEstimate))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRSqrtEstimateFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRSqrtStep))); // A32 only. + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPRSqrtStepFused))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPSqrt))); + SetDelegateInfo(typeof(SoftFloat32).GetMethod(nameof(SoftFloat32.FPSub))); + + SetDelegateInfo(typeof(SoftFloat32_16).GetMethod(nameof(SoftFloat32_16.FPConvert))); + + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPAdd))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPAddFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompare))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareEQ))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareEQFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareGE))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareGEFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareGT))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareGTFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareLE))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareLEFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareLT))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPCompareLTFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPDiv))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMax))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMaxFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMaxNum))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMaxNumFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMin))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMinFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMinNum))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMinNumFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMul))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMulFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMulAdd))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMulAddFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMulSub))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMulSubFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPMulX))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPNegMulAdd))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPNegMulSub))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRecipEstimate))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRecipEstimateFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRecipStep))); // A32 only. + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRecipStepFused))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRecpX))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRSqrtEstimate))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRSqrtEstimateFpscr))); // A32 only. + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRSqrtStep))); // A32 only. + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPRSqrtStepFused))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPSqrt))); + SetDelegateInfo(typeof(SoftFloat64).GetMethod(nameof(SoftFloat64.FPSub))); + + SetDelegateInfo(typeof(SoftFloat64_16).GetMethod(nameof(SoftFloat64_16.FPConvert))); + } + } +} diff --git a/src/ARMeilleure/Translation/DispatcherFunction.cs b/src/ARMeilleure/Translation/DispatcherFunction.cs new file mode 100644 index 00000000..7d5a3388 --- /dev/null +++ b/src/ARMeilleure/Translation/DispatcherFunction.cs @@ -0,0 +1,7 @@ +using System; + +namespace ARMeilleure.Translation +{ + delegate void DispatcherFunction(IntPtr nativeContext, ulong startAddress); + delegate ulong WrapperFunction(IntPtr nativeContext, ulong startAddress); +} diff --git a/src/ARMeilleure/Translation/Dominance.cs b/src/ARMeilleure/Translation/Dominance.cs new file mode 100644 index 00000000..b9b961d1 --- /dev/null +++ b/src/ARMeilleure/Translation/Dominance.cs @@ -0,0 +1,95 @@ +using ARMeilleure.IntermediateRepresentation; +using System.Diagnostics; + +namespace ARMeilleure.Translation +{ + static class Dominance + { + // Those methods are an implementation of the algorithms on "A Simple, Fast Dominance Algorithm". + // https://www.cs.rice.edu/~keith/EMBED/dom.pdf + public static void FindDominators(ControlFlowGraph cfg) + { + BasicBlock Intersect(BasicBlock block1, BasicBlock block2) + { + while (block1 != block2) + { + while (cfg.PostOrderMap[block1.Index] < cfg.PostOrderMap[block2.Index]) + { + block1 = block1.ImmediateDominator; + } + + while (cfg.PostOrderMap[block2.Index] < cfg.PostOrderMap[block1.Index]) + { + block2 = block2.ImmediateDominator; + } + } + + return block1; + } + + cfg.Entry.ImmediateDominator = cfg.Entry; + + Debug.Assert(cfg.Entry == cfg.PostOrderBlocks[cfg.PostOrderBlocks.Length - 1]); + + bool modified; + + do + { + modified = false; + + for (int blkIndex = cfg.PostOrderBlocks.Length - 2; blkIndex >= 0; blkIndex--) + { + BasicBlock block = cfg.PostOrderBlocks[blkIndex]; + + BasicBlock newIDom = null; + + foreach (BasicBlock predecessor in block.Predecessors) + { + if (predecessor.ImmediateDominator != null) + { + if (newIDom != null) + { + newIDom = Intersect(predecessor, newIDom); + } + else + { + newIDom = predecessor; + } + } + } + + if (block.ImmediateDominator != newIDom) + { + block.ImmediateDominator = newIDom; + + modified = true; + } + } + } + while (modified); + } + + public static void FindDominanceFrontiers(ControlFlowGraph cfg) + { + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + if (block.Predecessors.Count < 2) + { + continue; + } + + for (int pBlkIndex = 0; pBlkIndex < block.Predecessors.Count; pBlkIndex++) + { + BasicBlock current = block.Predecessors[pBlkIndex]; + + while (current != block.ImmediateDominator) + { + current.DominanceFrontiers.Add(block); + + current = current.ImmediateDominator; + } + } + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/EmitterContext.cs b/src/ARMeilleure/Translation/EmitterContext.cs new file mode 100644 index 00000000..8fcb4dee --- /dev/null +++ b/src/ARMeilleure/Translation/EmitterContext.cs @@ -0,0 +1,680 @@ +using ARMeilleure.Diagnostics; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using System; +using System.Collections.Generic; +using System.Reflection; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Translation +{ + class EmitterContext + { + private int _localsCount; + + private readonly Dictionary _irLabels; + private readonly IntrusiveList _irBlocks; + + private BasicBlock _irBlock; + private BasicBlock _ifBlock; + + private bool _needsNewBlock; + private BasicBlockFrequency _nextBlockFreq; + + public EmitterContext() + { + _localsCount = 0; + + _irLabels = new Dictionary(); + _irBlocks = new IntrusiveList(); + + _needsNewBlock = true; + _nextBlockFreq = BasicBlockFrequency.Default; + } + + public Operand AllocateLocal(OperandType type) + { + Operand local = Local(type); + + local.NumberLocal(++_localsCount); + + return local; + } + + public Operand Add(Operand op1, Operand op2) + { + return Add(Instruction.Add, Local(op1.Type), op1, op2); + } + + public Operand BitwiseAnd(Operand op1, Operand op2) + { + return Add(Instruction.BitwiseAnd, Local(op1.Type), op1, op2); + } + + public Operand BitwiseExclusiveOr(Operand op1, Operand op2) + { + return Add(Instruction.BitwiseExclusiveOr, Local(op1.Type), op1, op2); + } + + public Operand BitwiseNot(Operand op1) + { + return Add(Instruction.BitwiseNot, Local(op1.Type), op1); + } + + public Operand BitwiseOr(Operand op1, Operand op2) + { + return Add(Instruction.BitwiseOr, Local(op1.Type), op1, op2); + } + + public void Branch(Operand label) + { + NewNextBlockIfNeeded(); + + BranchToLabel(label, uncond: true, BasicBlockFrequency.Default); + } + + public void BranchIf(Operand label, Operand op1, Operand op2, Comparison comp, BasicBlockFrequency falseFreq = default) + { + Add(Instruction.BranchIf, default, op1, op2, Const((int)comp)); + + BranchToLabel(label, uncond: false, falseFreq); + } + + public void BranchIfFalse(Operand label, Operand op1, BasicBlockFrequency falseFreq = default) + { + BranchIf(label, op1, Const(op1.Type, 0), Comparison.Equal, falseFreq); + } + + public void BranchIfTrue(Operand label, Operand op1, BasicBlockFrequency falseFreq = default) + { + BranchIf(label, op1, Const(op1.Type, 0), Comparison.NotEqual, falseFreq); + } + + public Operand ByteSwap(Operand op1) + { + return Add(Instruction.ByteSwap, Local(op1.Type), op1); + } + + public virtual Operand Call(MethodInfo info, params Operand[] callArgs) + { + IntPtr funcPtr = Delegates.GetDelegateFuncPtr(info); + + OperandType returnType = GetOperandType(info.ReturnType); + + Symbols.Add((ulong)funcPtr.ToInt64(), info.Name); + + return Call(Const(funcPtr.ToInt64()), returnType, callArgs); + } + + protected static OperandType GetOperandType(Type type) + { + if (type == typeof(bool) || type == typeof(byte) || + type == typeof(char) || type == typeof(short) || + type == typeof(int) || type == typeof(sbyte) || + type == typeof(ushort) || type == typeof(uint)) + { + return OperandType.I32; + } + else if (type == typeof(long) || type == typeof(ulong)) + { + return OperandType.I64; + } + else if (type == typeof(double)) + { + return OperandType.FP64; + } + else if (type == typeof(float)) + { + return OperandType.FP32; + } + else if (type == typeof(V128)) + { + return OperandType.V128; + } + else if (type == typeof(void)) + { + return OperandType.None; + } + else + { + throw new ArgumentException($"Invalid type \"{type.Name}\"."); + } + } + + public Operand Call(Operand address, OperandType returnType, params Operand[] callArgs) + { + Operand[] args = new Operand[callArgs.Length + 1]; + + args[0] = address; + + Array.Copy(callArgs, 0, args, 1, callArgs.Length); + + if (returnType != OperandType.None) + { + return Add(Instruction.Call, Local(returnType), args); + } + else + { + return Add(Instruction.Call, default, args); + } + } + + public void Tailcall(Operand address, params Operand[] callArgs) + { + Operand[] args = new Operand[callArgs.Length + 1]; + + args[0] = address; + + Array.Copy(callArgs, 0, args, 1, callArgs.Length); + + Add(Instruction.Tailcall, default, args); + + _needsNewBlock = true; + } + + public Operand CompareAndSwap(Operand address, Operand expected, Operand desired) + { + return Add(Instruction.CompareAndSwap, Local(desired.Type), address, expected, desired); + } + + public Operand CompareAndSwap16(Operand address, Operand expected, Operand desired) + { + return Add(Instruction.CompareAndSwap16, Local(OperandType.I32), address, expected, desired); + } + + public Operand CompareAndSwap8(Operand address, Operand expected, Operand desired) + { + return Add(Instruction.CompareAndSwap8, Local(OperandType.I32), address, expected, desired); + } + + public Operand ConditionalSelect(Operand op1, Operand op2, Operand op3) + { + return Add(Instruction.ConditionalSelect, Local(op2.Type), op1, op2, op3); + } + + public Operand ConvertI64ToI32(Operand op1) + { + if (op1.Type != OperandType.I64) + { + throw new ArgumentException($"Invalid operand type \"{op1.Type}\"."); + } + + return Add(Instruction.ConvertI64ToI32, Local(OperandType.I32), op1); + } + + public Operand ConvertToFP(OperandType type, Operand op1) + { + return Add(Instruction.ConvertToFP, Local(type), op1); + } + + public Operand ConvertToFPUI(OperandType type, Operand op1) + { + return Add(Instruction.ConvertToFPUI, Local(type), op1); + } + + public Operand Copy(Operand op1) + { + return Add(Instruction.Copy, Local(op1.Type), op1); + } + + public Operand Copy(Operand dest, Operand op1) + { + if (dest.Kind != OperandKind.Register && + (dest.Kind != OperandKind.LocalVariable || dest.GetLocalNumber() == 0)) + { + throw new ArgumentException($"Destination operand must be a Register or a numbered LocalVariable."); + } + + return Add(Instruction.Copy, dest, op1); + } + + public Operand CountLeadingZeros(Operand op1) + { + return Add(Instruction.CountLeadingZeros, Local(op1.Type), op1); + } + + public Operand Divide(Operand op1, Operand op2) + { + return Add(Instruction.Divide, Local(op1.Type), op1, op2); + } + + public Operand DivideUI(Operand op1, Operand op2) + { + return Add(Instruction.DivideUI, Local(op1.Type), op1, op2); + } + + public Operand ICompare(Operand op1, Operand op2, Comparison comp) + { + return Add(Instruction.Compare, Local(OperandType.I32), op1, op2, Const((int)comp)); + } + + public Operand ICompareEqual(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.Equal); + } + + public Operand ICompareGreater(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.Greater); + } + + public Operand ICompareGreaterOrEqual(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.GreaterOrEqual); + } + + public Operand ICompareGreaterOrEqualUI(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.GreaterOrEqualUI); + } + + public Operand ICompareGreaterUI(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.GreaterUI); + } + + public Operand ICompareLess(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.Less); + } + + public Operand ICompareLessOrEqual(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.LessOrEqual); + } + + public Operand ICompareLessOrEqualUI(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.LessOrEqualUI); + } + + public Operand ICompareLessUI(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.LessUI); + } + + public Operand ICompareNotEqual(Operand op1, Operand op2) + { + return ICompare(op1, op2, Comparison.NotEqual); + } + + public Operand Load(OperandType type, Operand address) + { + return Add(Instruction.Load, Local(type), address); + } + + public Operand Load16(Operand address) + { + return Add(Instruction.Load16, Local(OperandType.I32), address); + } + + public Operand Load8(Operand address) + { + return Add(Instruction.Load8, Local(OperandType.I32), address); + } + + public Operand LoadArgument(OperandType type, int index) + { + return Add(Instruction.LoadArgument, Local(type), Const(index)); + } + + public void LoadFromContext() + { + _needsNewBlock = true; + + Add(Instruction.LoadFromContext); + } + + public void MemoryBarrier() + { + Add(Instruction.MemoryBarrier); + } + + public Operand Multiply(Operand op1, Operand op2) + { + return Add(Instruction.Multiply, Local(op1.Type), op1, op2); + } + + public Operand Multiply64HighSI(Operand op1, Operand op2) + { + return Add(Instruction.Multiply64HighSI, Local(OperandType.I64), op1, op2); + } + + public Operand Multiply64HighUI(Operand op1, Operand op2) + { + return Add(Instruction.Multiply64HighUI, Local(OperandType.I64), op1, op2); + } + + public Operand Negate(Operand op1) + { + return Add(Instruction.Negate, Local(op1.Type), op1); + } + + public void Return() + { + Add(Instruction.Return); + + _needsNewBlock = true; + } + + public void Return(Operand op1) + { + Add(Instruction.Return, default, op1); + + _needsNewBlock = true; + } + + public Operand RotateRight(Operand op1, Operand op2) + { + return Add(Instruction.RotateRight, Local(op1.Type), op1, op2); + } + + public Operand ShiftLeft(Operand op1, Operand op2) + { + return Add(Instruction.ShiftLeft, Local(op1.Type), op1, op2); + } + + public Operand ShiftRightSI(Operand op1, Operand op2) + { + return Add(Instruction.ShiftRightSI, Local(op1.Type), op1, op2); + } + + public Operand ShiftRightUI(Operand op1, Operand op2) + { + return Add(Instruction.ShiftRightUI, Local(op1.Type), op1, op2); + } + + public Operand SignExtend16(OperandType type, Operand op1) + { + return Add(Instruction.SignExtend16, Local(type), op1); + } + + public Operand SignExtend32(OperandType type, Operand op1) + { + return Add(Instruction.SignExtend32, Local(type), op1); + } + + public Operand SignExtend8(OperandType type, Operand op1) + { + return Add(Instruction.SignExtend8, Local(type), op1); + } + + public void Store(Operand address, Operand value) + { + Add(Instruction.Store, default, address, value); + } + + public void Store16(Operand address, Operand value) + { + Add(Instruction.Store16, default, address, value); + } + + public void Store8(Operand address, Operand value) + { + Add(Instruction.Store8, default, address, value); + } + + public void StoreToContext() + { + Add(Instruction.StoreToContext); + + _needsNewBlock = true; + } + + public Operand Subtract(Operand op1, Operand op2) + { + return Add(Instruction.Subtract, Local(op1.Type), op1, op2); + } + + public Operand VectorCreateScalar(Operand value) + { + return Add(Instruction.VectorCreateScalar, Local(OperandType.V128), value); + } + + public Operand VectorExtract(OperandType type, Operand vector, int index) + { + return Add(Instruction.VectorExtract, Local(type), vector, Const(index)); + } + + public Operand VectorExtract16(Operand vector, int index) + { + return Add(Instruction.VectorExtract16, Local(OperandType.I32), vector, Const(index)); + } + + public Operand VectorExtract8(Operand vector, int index) + { + return Add(Instruction.VectorExtract8, Local(OperandType.I32), vector, Const(index)); + } + + public Operand VectorInsert(Operand vector, Operand value, int index) + { + return Add(Instruction.VectorInsert, Local(OperandType.V128), vector, value, Const(index)); + } + + public Operand VectorInsert16(Operand vector, Operand value, int index) + { + return Add(Instruction.VectorInsert16, Local(OperandType.V128), vector, value, Const(index)); + } + + public Operand VectorInsert8(Operand vector, Operand value, int index) + { + return Add(Instruction.VectorInsert8, Local(OperandType.V128), vector, value, Const(index)); + } + + public Operand VectorOne() + { + return Add(Instruction.VectorOne, Local(OperandType.V128)); + } + + public Operand VectorZero() + { + return Add(Instruction.VectorZero, Local(OperandType.V128)); + } + + public Operand VectorZeroUpper64(Operand vector) + { + return Add(Instruction.VectorZeroUpper64, Local(OperandType.V128), vector); + } + + public Operand VectorZeroUpper96(Operand vector) + { + return Add(Instruction.VectorZeroUpper96, Local(OperandType.V128), vector); + } + + public Operand ZeroExtend16(OperandType type, Operand op1) + { + return Add(Instruction.ZeroExtend16, Local(type), op1); + } + + public Operand ZeroExtend32(OperandType type, Operand op1) + { + return Add(Instruction.ZeroExtend32, Local(type), op1); + } + + public Operand ZeroExtend8(OperandType type, Operand op1) + { + return Add(Instruction.ZeroExtend8, Local(type), op1); + } + + private void NewNextBlockIfNeeded() + { + if (_needsNewBlock) + { + NewNextBlock(); + } + } + + private Operand Add(Instruction inst, Operand dest = default) + { + NewNextBlockIfNeeded(); + + Operation operation = Operation.Factory.Operation(inst, dest); + + _irBlock.Operations.AddLast(operation); + + return dest; + } + + private Operand Add(Instruction inst, Operand dest, Operand[] sources) + { + NewNextBlockIfNeeded(); + + Operation operation = Operation.Factory.Operation(inst, dest, sources); + + _irBlock.Operations.AddLast(operation); + + return dest; + } + + private Operand Add(Instruction inst, Operand dest, Operand source0) + { + NewNextBlockIfNeeded(); + + Operation operation = Operation.Factory.Operation(inst, dest, source0); + + _irBlock.Operations.AddLast(operation); + + return dest; + } + + private Operand Add(Instruction inst, Operand dest, Operand source0, Operand source1) + { + NewNextBlockIfNeeded(); + + Operation operation = Operation.Factory.Operation(inst, dest, source0, source1); + + _irBlock.Operations.AddLast(operation); + + return dest; + } + + private Operand Add(Instruction inst, Operand dest, Operand source0, Operand source1, Operand source2) + { + NewNextBlockIfNeeded(); + + Operation operation = Operation.Factory.Operation(inst, dest, source0, source1, source2); + + _irBlock.Operations.AddLast(operation); + + return dest; + } + + public Operand AddIntrinsic(Intrinsic intrin, params Operand[] args) + { + return Add(intrin, Local(OperandType.V128), args); + } + + public Operand AddIntrinsicInt(Intrinsic intrin, params Operand[] args) + { + return Add(intrin, Local(OperandType.I32), args); + } + + public Operand AddIntrinsicLong(Intrinsic intrin, params Operand[] args) + { + return Add(intrin, Local(OperandType.I64), args); + } + + public void AddIntrinsicNoRet(Intrinsic intrin, params Operand[] args) + { + Add(intrin, default, args); + } + + private Operand Add(Intrinsic intrin, Operand dest, params Operand[] sources) + { + NewNextBlockIfNeeded(); + + Operation operation = Operation.Factory.Operation(intrin, dest, sources); + + _irBlock.Operations.AddLast(operation); + + return dest; + } + + private void BranchToLabel(Operand label, bool uncond, BasicBlockFrequency nextFreq) + { + if (!_irLabels.TryGetValue(label, out BasicBlock branchBlock)) + { + branchBlock = new BasicBlock(); + + _irLabels.Add(label, branchBlock); + } + + if (uncond) + { + _irBlock.AddSuccessor(branchBlock); + } + else + { + // Defer registration of successor to _irBlock so that the order of successors is correct. + _ifBlock = branchBlock; + } + + _needsNewBlock = true; + _nextBlockFreq = nextFreq; + } + + public void MarkLabel(Operand label, BasicBlockFrequency nextFreq = default) + { + _nextBlockFreq = nextFreq; + + if (_irLabels.TryGetValue(label, out BasicBlock nextBlock)) + { + nextBlock.Index = _irBlocks.Count; + + _irBlocks.AddLast(nextBlock); + + NextBlock(nextBlock); + } + else + { + NewNextBlock(); + + _irLabels.Add(label, _irBlock); + } + } + + private void NewNextBlock() + { + BasicBlock block = new BasicBlock(_irBlocks.Count); + + _irBlocks.AddLast(block); + + NextBlock(block); + } + + private void NextBlock(BasicBlock nextBlock) + { + if (_irBlock?.SuccessorsCount == 0 && !EndsWithUnconditional(_irBlock)) + { + _irBlock.AddSuccessor(nextBlock); + + if (_ifBlock != null) + { + _irBlock.AddSuccessor(_ifBlock); + + _ifBlock = null; + } + } + + _irBlock = nextBlock; + _irBlock.Frequency = _nextBlockFreq; + + _needsNewBlock = false; + _nextBlockFreq = BasicBlockFrequency.Default; + } + + private static bool EndsWithUnconditional(BasicBlock block) + { + Operation last = block.Operations.Last; + + return last != default && + (last.Instruction == Instruction.Return || + last.Instruction == Instruction.Tailcall); + } + + public ControlFlowGraph GetControlFlowGraph() + { + return new ControlFlowGraph(_irBlocks.First, _irBlocks, _localsCount); + } + } +} diff --git a/src/ARMeilleure/Translation/GuestFunction.cs b/src/ARMeilleure/Translation/GuestFunction.cs new file mode 100644 index 00000000..ac131a0d --- /dev/null +++ b/src/ARMeilleure/Translation/GuestFunction.cs @@ -0,0 +1,6 @@ +using System; + +namespace ARMeilleure.Translation +{ + delegate ulong GuestFunction(IntPtr nativeContextPtr); +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/IntervalTree.cs b/src/ARMeilleure/Translation/IntervalTree.cs new file mode 100644 index 00000000..9af01bea --- /dev/null +++ b/src/ARMeilleure/Translation/IntervalTree.cs @@ -0,0 +1,745 @@ +using System; +using System.Collections.Generic; + +namespace ARMeilleure.Translation +{ + /// + /// An Augmented Interval Tree based off of the "TreeDictionary"'s Red-Black Tree. Allows fast overlap checking of ranges. + /// + /// Key + /// Value + class IntervalTree where K : IComparable + { + private const int ArrayGrowthSize = 32; + + private const bool Black = true; + private const bool Red = false; + private IntervalTreeNode _root = null; + private int _count = 0; + + public int Count => _count; + + #region Public Methods + + /// + /// Gets the values of the interval whose key is . + /// + /// Key of the node value to get + /// Value with the given + /// True if the key is on the dictionary, false otherwise + public bool TryGet(K key, out V value) + { + IntervalTreeNode node = GetNode(key); + + if (node == null) + { + value = default; + return false; + } + + value = node.Value; + return true; + } + + /// + /// Returns the start addresses of the intervals whose start and end keys overlap the given range. + /// + /// Start of the range + /// End of the range + /// Overlaps array to place results in + /// Index to start writing results into the array. Defaults to 0 + /// Number of intervals found + public int Get(K start, K end, ref K[] overlaps, int overlapCount = 0) + { + GetKeys(_root, start, end, ref overlaps, ref overlapCount); + + return overlapCount; + } + + /// + /// Adds a new interval into the tree whose start is , end is and value is . + /// + /// Start of the range to add + /// End of the range to insert + /// Value to add + /// Optional factory used to create a new value if is already on the tree + /// is null + /// True if the value was added, false if the start key was already in the dictionary + public bool AddOrUpdate(K start, K end, V value, Func updateFactoryCallback) + { + ArgumentNullException.ThrowIfNull(value); + + return BSTInsert(start, end, value, updateFactoryCallback, out IntervalTreeNode node); + } + + /// + /// Gets an existing or adds a new interval into the tree whose start is , end is and value is . + /// + /// Start of the range to add + /// End of the range to insert + /// Value to add + /// is null + /// if is not yet on the tree, or the existing value otherwise + public V GetOrAdd(K start, K end, V value) + { + ArgumentNullException.ThrowIfNull(value); + + BSTInsert(start, end, value, null, out IntervalTreeNode node); + return node.Value; + } + + /// + /// Removes a value from the tree, searching for it with . + /// + /// Key of the node to remove + /// Number of deleted values + public int Remove(K key) + { + int removed = Delete(key); + + _count -= removed; + + return removed; + } + + /// + /// Adds all the nodes in the dictionary into . + /// + /// A list of all values sorted by Key Order + public List AsList() + { + List list = new List(); + + AddToList(_root, list); + + return list; + } + + #endregion + + #region Private Methods (BST) + + /// + /// Adds all values that are children of or contained within into , in Key Order. + /// + /// The node to search for values within + /// The list to add values to + private void AddToList(IntervalTreeNode node, List list) + { + if (node == null) + { + return; + } + + AddToList(node.Left, list); + + list.Add(node.Value); + + AddToList(node.Right, list); + } + + /// + /// Retrieve the node reference whose key is , or null if no such node exists. + /// + /// Key of the node to get + /// is null + /// Node reference in the tree + private IntervalTreeNode GetNode(K key) + { + ArgumentNullException.ThrowIfNull(key); + + IntervalTreeNode node = _root; + while (node != null) + { + int cmp = key.CompareTo(node.Start); + if (cmp < 0) + { + node = node.Left; + } + else if (cmp > 0) + { + node = node.Right; + } + else + { + return node; + } + } + return null; + } + + /// + /// Retrieve all keys that overlap the given start and end keys. + /// + /// Start of the range + /// End of the range + /// Overlaps array to place results in + /// Overlaps count to update + private void GetKeys(IntervalTreeNode node, K start, K end, ref K[] overlaps, ref int overlapCount) + { + if (node == null || start.CompareTo(node.Max) >= 0) + { + return; + } + + GetKeys(node.Left, start, end, ref overlaps, ref overlapCount); + + bool endsOnRight = end.CompareTo(node.Start) > 0; + if (endsOnRight) + { + if (start.CompareTo(node.End) < 0) + { + if (overlaps.Length >= overlapCount) + { + Array.Resize(ref overlaps, overlapCount + ArrayGrowthSize); + } + + overlaps[overlapCount++] = node.Start; + } + + GetKeys(node.Right, start, end, ref overlaps, ref overlapCount); + } + } + + /// + /// Propagate an increase in max value starting at the given node, heading up the tree. + /// This should only be called if the max increases - not for rebalancing or removals. + /// + /// The node to start propagating from + private void PropagateIncrease(IntervalTreeNode node) + { + K max = node.Max; + IntervalTreeNode ptr = node; + + while ((ptr = ptr.Parent) != null) + { + if (max.CompareTo(ptr.Max) > 0) + { + ptr.Max = max; + } + else + { + break; + } + } + } + + /// + /// Propagate recalculating max value starting at the given node, heading up the tree. + /// This fully recalculates the max value from all children when there is potential for it to decrease. + /// + /// The node to start propagating from + private void PropagateFull(IntervalTreeNode node) + { + IntervalTreeNode ptr = node; + + do + { + K max = ptr.End; + + if (ptr.Left != null && ptr.Left.Max.CompareTo(max) > 0) + { + max = ptr.Left.Max; + } + + if (ptr.Right != null && ptr.Right.Max.CompareTo(max) > 0) + { + max = ptr.Right.Max; + } + + ptr.Max = max; + } while ((ptr = ptr.Parent) != null); + } + + /// + /// Insertion Mechanism for the interval tree. Similar to a BST insert, with the start of the range as the key. + /// Iterates the tree starting from the root and inserts a new node where all children in the left subtree are less than , and all children in the right subtree are greater than . + /// Each node can contain multiple values, and has an end address which is the maximum of all those values. + /// Post insertion, the "max" value of the node and all parents are updated. + /// + /// Start of the range to insert + /// End of the range to insert + /// Value to insert + /// Optional factory used to create a new value if is already on the tree + /// Node that was inserted or modified + /// True if was not yet on the tree, false otherwise + private bool BSTInsert(K start, K end, V value, Func updateFactoryCallback, out IntervalTreeNode outNode) + { + IntervalTreeNode parent = null; + IntervalTreeNode node = _root; + + while (node != null) + { + parent = node; + int cmp = start.CompareTo(node.Start); + if (cmp < 0) + { + node = node.Left; + } + else if (cmp > 0) + { + node = node.Right; + } + else + { + outNode = node; + + if (updateFactoryCallback != null) + { + // Replace + node.Value = updateFactoryCallback(start, node.Value); + + int endCmp = end.CompareTo(node.End); + + if (endCmp > 0) + { + node.End = end; + if (end.CompareTo(node.Max) > 0) + { + node.Max = end; + PropagateIncrease(node); + RestoreBalanceAfterInsertion(node); + } + } + else if (endCmp < 0) + { + node.End = end; + PropagateFull(node); + } + } + + return false; + } + } + IntervalTreeNode newNode = new IntervalTreeNode(start, end, value, parent); + if (newNode.Parent == null) + { + _root = newNode; + } + else if (start.CompareTo(parent.Start) < 0) + { + parent.Left = newNode; + } + else + { + parent.Right = newNode; + } + + PropagateIncrease(newNode); + _count++; + RestoreBalanceAfterInsertion(newNode); + outNode = newNode; + return true; + } + + /// + /// Removes the value from the dictionary after searching for it with . + /// + /// Key to search for + /// Number of deleted values + private int Delete(K key) + { + IntervalTreeNode nodeToDelete = GetNode(key); + + if (nodeToDelete == null) + { + return 0; + } + + IntervalTreeNode replacementNode; + + if (LeftOf(nodeToDelete) == null || RightOf(nodeToDelete) == null) + { + replacementNode = nodeToDelete; + } + else + { + replacementNode = PredecessorOf(nodeToDelete); + } + + IntervalTreeNode tmp = LeftOf(replacementNode) ?? RightOf(replacementNode); + + if (tmp != null) + { + tmp.Parent = ParentOf(replacementNode); + } + + if (ParentOf(replacementNode) == null) + { + _root = tmp; + } + else if (replacementNode == LeftOf(ParentOf(replacementNode))) + { + ParentOf(replacementNode).Left = tmp; + } + else + { + ParentOf(replacementNode).Right = tmp; + } + + if (replacementNode != nodeToDelete) + { + nodeToDelete.Start = replacementNode.Start; + nodeToDelete.Value = replacementNode.Value; + nodeToDelete.End = replacementNode.End; + nodeToDelete.Max = replacementNode.Max; + } + + PropagateFull(replacementNode); + + if (tmp != null && ColorOf(replacementNode) == Black) + { + RestoreBalanceAfterRemoval(tmp); + } + + return 1; + } + + /// + /// Returns the node with the largest key where is considered the root node. + /// + /// Root Node + /// Node with the maximum key in the tree of + private static IntervalTreeNode Maximum(IntervalTreeNode node) + { + IntervalTreeNode tmp = node; + while (tmp.Right != null) + { + tmp = tmp.Right; + } + + return tmp; + } + + /// + /// Finds the node whose key is immediately less than . + /// + /// Node to find the predecessor of + /// Predecessor of + private static IntervalTreeNode PredecessorOf(IntervalTreeNode node) + { + if (node.Left != null) + { + return Maximum(node.Left); + } + IntervalTreeNode parent = node.Parent; + while (parent != null && node == parent.Left) + { + node = parent; + parent = parent.Parent; + } + return parent; + } + + #endregion + + #region Private Methods (RBL) + + private void RestoreBalanceAfterRemoval(IntervalTreeNode balanceNode) + { + IntervalTreeNode ptr = balanceNode; + + while (ptr != _root && ColorOf(ptr) == Black) + { + if (ptr == LeftOf(ParentOf(ptr))) + { + IntervalTreeNode sibling = RightOf(ParentOf(ptr)); + + if (ColorOf(sibling) == Red) + { + SetColor(sibling, Black); + SetColor(ParentOf(ptr), Red); + RotateLeft(ParentOf(ptr)); + sibling = RightOf(ParentOf(ptr)); + } + if (ColorOf(LeftOf(sibling)) == Black && ColorOf(RightOf(sibling)) == Black) + { + SetColor(sibling, Red); + ptr = ParentOf(ptr); + } + else + { + if (ColorOf(RightOf(sibling)) == Black) + { + SetColor(LeftOf(sibling), Black); + SetColor(sibling, Red); + RotateRight(sibling); + sibling = RightOf(ParentOf(ptr)); + } + SetColor(sibling, ColorOf(ParentOf(ptr))); + SetColor(ParentOf(ptr), Black); + SetColor(RightOf(sibling), Black); + RotateLeft(ParentOf(ptr)); + ptr = _root; + } + } + else + { + IntervalTreeNode sibling = LeftOf(ParentOf(ptr)); + + if (ColorOf(sibling) == Red) + { + SetColor(sibling, Black); + SetColor(ParentOf(ptr), Red); + RotateRight(ParentOf(ptr)); + sibling = LeftOf(ParentOf(ptr)); + } + if (ColorOf(RightOf(sibling)) == Black && ColorOf(LeftOf(sibling)) == Black) + { + SetColor(sibling, Red); + ptr = ParentOf(ptr); + } + else + { + if (ColorOf(LeftOf(sibling)) == Black) + { + SetColor(RightOf(sibling), Black); + SetColor(sibling, Red); + RotateLeft(sibling); + sibling = LeftOf(ParentOf(ptr)); + } + SetColor(sibling, ColorOf(ParentOf(ptr))); + SetColor(ParentOf(ptr), Black); + SetColor(LeftOf(sibling), Black); + RotateRight(ParentOf(ptr)); + ptr = _root; + } + } + } + SetColor(ptr, Black); + } + + private void RestoreBalanceAfterInsertion(IntervalTreeNode balanceNode) + { + SetColor(balanceNode, Red); + while (balanceNode != null && balanceNode != _root && ColorOf(ParentOf(balanceNode)) == Red) + { + if (ParentOf(balanceNode) == LeftOf(ParentOf(ParentOf(balanceNode)))) + { + IntervalTreeNode sibling = RightOf(ParentOf(ParentOf(balanceNode))); + + if (ColorOf(sibling) == Red) + { + SetColor(ParentOf(balanceNode), Black); + SetColor(sibling, Black); + SetColor(ParentOf(ParentOf(balanceNode)), Red); + balanceNode = ParentOf(ParentOf(balanceNode)); + } + else + { + if (balanceNode == RightOf(ParentOf(balanceNode))) + { + balanceNode = ParentOf(balanceNode); + RotateLeft(balanceNode); + } + SetColor(ParentOf(balanceNode), Black); + SetColor(ParentOf(ParentOf(balanceNode)), Red); + RotateRight(ParentOf(ParentOf(balanceNode))); + } + } + else + { + IntervalTreeNode sibling = LeftOf(ParentOf(ParentOf(balanceNode))); + + if (ColorOf(sibling) == Red) + { + SetColor(ParentOf(balanceNode), Black); + SetColor(sibling, Black); + SetColor(ParentOf(ParentOf(balanceNode)), Red); + balanceNode = ParentOf(ParentOf(balanceNode)); + } + else + { + if (balanceNode == LeftOf(ParentOf(balanceNode))) + { + balanceNode = ParentOf(balanceNode); + RotateRight(balanceNode); + } + SetColor(ParentOf(balanceNode), Black); + SetColor(ParentOf(ParentOf(balanceNode)), Red); + RotateLeft(ParentOf(ParentOf(balanceNode))); + } + } + } + SetColor(_root, Black); + } + + private void RotateLeft(IntervalTreeNode node) + { + if (node != null) + { + IntervalTreeNode right = RightOf(node); + node.Right = LeftOf(right); + if (node.Right != null) + { + node.Right.Parent = node; + } + IntervalTreeNode nodeParent = ParentOf(node); + right.Parent = nodeParent; + if (nodeParent == null) + { + _root = right; + } + else if (node == LeftOf(nodeParent)) + { + nodeParent.Left = right; + } + else + { + nodeParent.Right = right; + } + right.Left = node; + node.Parent = right; + + PropagateFull(node); + } + } + + private void RotateRight(IntervalTreeNode node) + { + if (node != null) + { + IntervalTreeNode left = LeftOf(node); + node.Left = RightOf(left); + if (node.Left != null) + { + node.Left.Parent = node; + } + IntervalTreeNode nodeParent = ParentOf(node); + left.Parent = nodeParent; + if (nodeParent == null) + { + _root = left; + } + else if (node == RightOf(nodeParent)) + { + nodeParent.Right = left; + } + else + { + nodeParent.Left = left; + } + left.Right = node; + node.Parent = left; + + PropagateFull(node); + } + } + + #endregion + + #region Safety-Methods + + // These methods save memory by allowing us to forego sentinel nil nodes, as well as serve as protection against NullReferenceExceptions. + + /// + /// Returns the color of , or Black if it is null. + /// + /// Node + /// The boolean color of , or black if null + private static bool ColorOf(IntervalTreeNode node) + { + return node == null || node.Color; + } + + /// + /// Sets the color of node to . + ///

+ /// This method does nothing if is null. + ///
+ /// Node to set the color of + /// Color (Boolean) + private static void SetColor(IntervalTreeNode node, bool color) + { + if (node != null) + { + node.Color = color; + } + } + + /// + /// This method returns the left node of , or null if is null. + /// + /// Node to retrieve the left child from + /// Left child of + private static IntervalTreeNode LeftOf(IntervalTreeNode node) + { + return node?.Left; + } + + /// + /// This method returns the right node of , or null if is null. + /// + /// Node to retrieve the right child from + /// Right child of + private static IntervalTreeNode RightOf(IntervalTreeNode node) + { + return node?.Right; + } + + /// + /// Returns the parent node of , or null if is null. + /// + /// Node to retrieve the parent from + /// Parent of + private static IntervalTreeNode ParentOf(IntervalTreeNode node) + { + return node?.Parent; + } + + #endregion + + public bool ContainsKey(K key) + { + return GetNode(key) != null; + } + + public void Clear() + { + _root = null; + _count = 0; + } + } + + /// + /// Represents a node in the IntervalTree which contains start and end keys of type K, and a value of generic type V. + /// + /// Key type of the node + /// Value type of the node + class IntervalTreeNode + { + public bool Color = true; + public IntervalTreeNode Left = null; + public IntervalTreeNode Right = null; + public IntervalTreeNode Parent = null; + + /// + /// The start of the range. + /// + public K Start; + + /// + /// The end of the range. + /// + public K End; + + /// + /// The maximum end value of this node and all its children. + /// + public K Max; + + /// + /// Value stored on this node. + /// + public V Value; + + public IntervalTreeNode(K start, K end, V value, IntervalTreeNode parent) + { + Start = start; + End = end; + Max = end; + Value = value; + Parent = parent; + } + } +} diff --git a/src/ARMeilleure/Translation/PTC/EncodingCache.cs b/src/ARMeilleure/Translation/PTC/EncodingCache.cs new file mode 100644 index 00000000..90d40c47 --- /dev/null +++ b/src/ARMeilleure/Translation/PTC/EncodingCache.cs @@ -0,0 +1,9 @@ +using System.Text; + +namespace ARMeilleure.Translation.PTC +{ + static class EncodingCache + { + public static readonly Encoding UTF8NoBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/PTC/IPtcLoadState.cs b/src/ARMeilleure/Translation/PTC/IPtcLoadState.cs new file mode 100644 index 00000000..1b11ac0b --- /dev/null +++ b/src/ARMeilleure/Translation/PTC/IPtcLoadState.cs @@ -0,0 +1,10 @@ +using System; + +namespace ARMeilleure.Translation.PTC +{ + public interface IPtcLoadState + { + event Action PtcStateChanged; + void Continue(); + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/PTC/Ptc.cs b/src/ARMeilleure/Translation/PTC/Ptc.cs new file mode 100644 index 00000000..ea4e715b --- /dev/null +++ b/src/ARMeilleure/Translation/PTC/Ptc.cs @@ -0,0 +1,1131 @@ +using ARMeilleure.CodeGen; +using ARMeilleure.CodeGen.Linking; +using ARMeilleure.CodeGen.Unwinding; +using ARMeilleure.Common; +using ARMeilleure.Memory; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Runtime; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +using static ARMeilleure.Translation.PTC.PtcFormatter; + +namespace ARMeilleure.Translation.PTC +{ + using Arm64HardwareCapabilities = ARMeilleure.CodeGen.Arm64.HardwareCapabilities; + using X86HardwareCapabilities = ARMeilleure.CodeGen.X86.HardwareCapabilities; + + class Ptc : IPtcLoadState + { + private const string OuterHeaderMagicString = "PTCohd\0\0"; + private const string InnerHeaderMagicString = "PTCihd\0\0"; + + private const uint InternalVersion = 4661; //! To be incremented manually for each change to the ARMeilleure project. + + private const string ActualDir = "0"; + private const string BackupDir = "1"; + + private const string TitleIdTextDefault = "0000000000000000"; + private const string DisplayVersionDefault = "0"; + + public static readonly Symbol PageTableSymbol = new(SymbolType.Special, 1); + public static readonly Symbol CountTableSymbol = new(SymbolType.Special, 2); + public static readonly Symbol DispatchStubSymbol = new(SymbolType.Special, 3); + + private const byte FillingByte = 0x00; + private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest; + + public PtcProfiler Profiler { get; } + + // Carriers. + private MemoryStream _infosStream; + private List _codesList; + private MemoryStream _relocsStream; + private MemoryStream _unwindInfosStream; + + private readonly ulong _outerHeaderMagic; + private readonly ulong _innerHeaderMagic; + + private readonly ManualResetEvent _waitEvent; + + private readonly object _lock; + + private bool _disposed; + + public string TitleIdText { get; private set; } + public string DisplayVersion { get; private set; } + + private MemoryManagerType _memoryMode; + + public string CachePathActual { get; private set; } + public string CachePathBackup { get; private set; } + + public PtcState State { get; private set; } + + // Progress reporting helpers. + private volatile int _translateCount; + private volatile int _translateTotalCount; + public event Action PtcStateChanged; + + public Ptc() + { + Profiler = new PtcProfiler(this); + + InitializeCarriers(); + + _outerHeaderMagic = BinaryPrimitives.ReadUInt64LittleEndian(EncodingCache.UTF8NoBOM.GetBytes(OuterHeaderMagicString).AsSpan()); + _innerHeaderMagic = BinaryPrimitives.ReadUInt64LittleEndian(EncodingCache.UTF8NoBOM.GetBytes(InnerHeaderMagicString).AsSpan()); + + _waitEvent = new ManualResetEvent(true); + + _lock = new object(); + + _disposed = false; + + TitleIdText = TitleIdTextDefault; + DisplayVersion = DisplayVersionDefault; + + CachePathActual = string.Empty; + CachePathBackup = string.Empty; + + Disable(); + } + + public void Initialize(string titleIdText, string displayVersion, bool enabled, MemoryManagerType memoryMode) + { + Wait(); + + Profiler.Wait(); + Profiler.ClearEntries(); + + Logger.Info?.Print(LogClass.Ptc, $"Initializing Profiled Persistent Translation Cache (enabled: {enabled})."); + + if (!enabled || string.IsNullOrEmpty(titleIdText) || titleIdText == TitleIdTextDefault) + { + TitleIdText = TitleIdTextDefault; + DisplayVersion = DisplayVersionDefault; + + CachePathActual = string.Empty; + CachePathBackup = string.Empty; + + Disable(); + + return; + } + + TitleIdText = titleIdText; + DisplayVersion = !string.IsNullOrEmpty(displayVersion) ? displayVersion : DisplayVersionDefault; + _memoryMode = memoryMode; + + string workPathActual = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", ActualDir); + string workPathBackup = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", BackupDir); + + if (!Directory.Exists(workPathActual)) + { + Directory.CreateDirectory(workPathActual); + } + + if (!Directory.Exists(workPathBackup)) + { + Directory.CreateDirectory(workPathBackup); + } + + CachePathActual = Path.Combine(workPathActual, DisplayVersion); + CachePathBackup = Path.Combine(workPathBackup, DisplayVersion); + + PreLoad(); + Profiler.PreLoad(); + + Enable(); + } + + private void InitializeCarriers() + { + _infosStream = MemoryStreamManager.Shared.GetStream(); + _codesList = new List(); + _relocsStream = MemoryStreamManager.Shared.GetStream(); + _unwindInfosStream = MemoryStreamManager.Shared.GetStream(); + } + + private void DisposeCarriers() + { + _infosStream.Dispose(); + _codesList.Clear(); + _relocsStream.Dispose(); + _unwindInfosStream.Dispose(); + } + + private bool AreCarriersEmpty() + { + return _infosStream.Length == 0L && _codesList.Count == 0 && _relocsStream.Length == 0L && _unwindInfosStream.Length == 0L; + } + + private void ResetCarriersIfNeeded() + { + if (AreCarriersEmpty()) + { + return; + } + + DisposeCarriers(); + + InitializeCarriers(); + } + + private void PreLoad() + { + string fileNameActual = $"{CachePathActual}.cache"; + string fileNameBackup = $"{CachePathBackup}.cache"; + + FileInfo fileInfoActual = new FileInfo(fileNameActual); + FileInfo fileInfoBackup = new FileInfo(fileNameBackup); + + if (fileInfoActual.Exists && fileInfoActual.Length != 0L) + { + if (!Load(fileNameActual, false)) + { + if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L) + { + Load(fileNameBackup, true); + } + } + } + else if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L) + { + Load(fileNameBackup, true); + } + } + + private unsafe bool Load(string fileName, bool isBackup) + { + using (FileStream compressedStream = new(fileName, FileMode.Open)) + using (DeflateStream deflateStream = new(compressedStream, CompressionMode.Decompress, true)) + { + OuterHeader outerHeader = DeserializeStructure(compressedStream); + + if (!outerHeader.IsHeaderValid()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.Magic != _outerHeaderMagic) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.CacheFileVersion != InternalVersion) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.Endianness != GetEndianness()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.FeatureInfo != GetFeatureInfo()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.MemoryManagerMode != GetMemoryManagerMode()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.OSPlatform != GetOSPlatform()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.Architecture != (uint)RuntimeInformation.ProcessArchitecture) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + IntPtr intPtr = IntPtr.Zero; + + try + { + intPtr = Marshal.AllocHGlobal(new IntPtr(outerHeader.UncompressedStreamSize)); + + using (UnmanagedMemoryStream stream = new((byte*)intPtr.ToPointer(), outerHeader.UncompressedStreamSize, outerHeader.UncompressedStreamSize, FileAccess.ReadWrite)) + { + try + { + deflateStream.CopyTo(stream); + } + catch + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + Debug.Assert(stream.Position == stream.Length); + + stream.Seek(0L, SeekOrigin.Begin); + + InnerHeader innerHeader = DeserializeStructure(stream); + + if (!innerHeader.IsHeaderValid()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (innerHeader.Magic != _innerHeaderMagic) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + ReadOnlySpan infosBytes = new(stream.PositionPointer, innerHeader.InfosLength); + stream.Seek(innerHeader.InfosLength, SeekOrigin.Current); + + Hash128 infosHash = XXHash128.ComputeHash(infosBytes); + + if (innerHeader.InfosHash != infosHash) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + ReadOnlySpan codesBytes = (int)innerHeader.CodesLength > 0 ? new(stream.PositionPointer, (int)innerHeader.CodesLength) : ReadOnlySpan.Empty; + stream.Seek(innerHeader.CodesLength, SeekOrigin.Current); + + Hash128 codesHash = XXHash128.ComputeHash(codesBytes); + + if (innerHeader.CodesHash != codesHash) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + ReadOnlySpan relocsBytes = new(stream.PositionPointer, innerHeader.RelocsLength); + stream.Seek(innerHeader.RelocsLength, SeekOrigin.Current); + + Hash128 relocsHash = XXHash128.ComputeHash(relocsBytes); + + if (innerHeader.RelocsHash != relocsHash) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + ReadOnlySpan unwindInfosBytes = new(stream.PositionPointer, innerHeader.UnwindInfosLength); + stream.Seek(innerHeader.UnwindInfosLength, SeekOrigin.Current); + + Hash128 unwindInfosHash = XXHash128.ComputeHash(unwindInfosBytes); + + if (innerHeader.UnwindInfosHash != unwindInfosHash) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + Debug.Assert(stream.Position == stream.Length); + + stream.Seek((long)Unsafe.SizeOf(), SeekOrigin.Begin); + + _infosStream.Write(infosBytes); + stream.Seek(innerHeader.InfosLength, SeekOrigin.Current); + + _codesList.ReadFrom(stream); + + _relocsStream.Write(relocsBytes); + stream.Seek(innerHeader.RelocsLength, SeekOrigin.Current); + + _unwindInfosStream.Write(unwindInfosBytes); + stream.Seek(innerHeader.UnwindInfosLength, SeekOrigin.Current); + + Debug.Assert(stream.Position == stream.Length); + } + } + finally + { + if (intPtr != IntPtr.Zero) + { + Marshal.FreeHGlobal(intPtr); + } + } + } + + long fileSize = new FileInfo(fileName).Length; + + Logger.Info?.Print(LogClass.Ptc, $"{(isBackup ? "Loaded Backup Translation Cache" : "Loaded Translation Cache")} (size: {fileSize} bytes, translated functions: {GetEntriesCount()})."); + + return true; + } + + private void InvalidateCompressedStream(FileStream compressedStream) + { + compressedStream.SetLength(0L); + } + + private void PreSave() + { + _waitEvent.Reset(); + + try + { + string fileNameActual = $"{CachePathActual}.cache"; + string fileNameBackup = $"{CachePathBackup}.cache"; + + FileInfo fileInfoActual = new FileInfo(fileNameActual); + + if (fileInfoActual.Exists && fileInfoActual.Length != 0L) + { + File.Copy(fileNameActual, fileNameBackup, true); + } + + Save(fileNameActual); + } + finally + { + ResetCarriersIfNeeded(); + + GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; + } + + _waitEvent.Set(); + } + + private unsafe void Save(string fileName) + { + int translatedFuncsCount; + + InnerHeader innerHeader = new InnerHeader(); + + innerHeader.Magic = _innerHeaderMagic; + + innerHeader.InfosLength = (int)_infosStream.Length; + innerHeader.CodesLength = _codesList.Length(); + innerHeader.RelocsLength = (int)_relocsStream.Length; + innerHeader.UnwindInfosLength = (int)_unwindInfosStream.Length; + + OuterHeader outerHeader = new OuterHeader(); + + outerHeader.Magic = _outerHeaderMagic; + + outerHeader.CacheFileVersion = InternalVersion; + outerHeader.Endianness = GetEndianness(); + outerHeader.FeatureInfo = GetFeatureInfo(); + outerHeader.MemoryManagerMode = GetMemoryManagerMode(); + outerHeader.OSPlatform = GetOSPlatform(); + outerHeader.Architecture = (uint)RuntimeInformation.ProcessArchitecture; + + outerHeader.UncompressedStreamSize = + (long)Unsafe.SizeOf() + + innerHeader.InfosLength + + innerHeader.CodesLength + + innerHeader.RelocsLength + + innerHeader.UnwindInfosLength; + + outerHeader.SetHeaderHash(); + + IntPtr intPtr = IntPtr.Zero; + + try + { + intPtr = Marshal.AllocHGlobal(new IntPtr(outerHeader.UncompressedStreamSize)); + + using (UnmanagedMemoryStream stream = new((byte*)intPtr.ToPointer(), outerHeader.UncompressedStreamSize, outerHeader.UncompressedStreamSize, FileAccess.ReadWrite)) + { + stream.Seek((long)Unsafe.SizeOf(), SeekOrigin.Begin); + + ReadOnlySpan infosBytes = new(stream.PositionPointer, innerHeader.InfosLength); + _infosStream.WriteTo(stream); + + ReadOnlySpan codesBytes = (int)innerHeader.CodesLength > 0 ? new(stream.PositionPointer, (int)innerHeader.CodesLength) : ReadOnlySpan.Empty; + _codesList.WriteTo(stream); + + ReadOnlySpan relocsBytes = new(stream.PositionPointer, innerHeader.RelocsLength); + _relocsStream.WriteTo(stream); + + ReadOnlySpan unwindInfosBytes = new(stream.PositionPointer, innerHeader.UnwindInfosLength); + _unwindInfosStream.WriteTo(stream); + + Debug.Assert(stream.Position == stream.Length); + + innerHeader.InfosHash = XXHash128.ComputeHash(infosBytes); + innerHeader.CodesHash = XXHash128.ComputeHash(codesBytes); + innerHeader.RelocsHash = XXHash128.ComputeHash(relocsBytes); + innerHeader.UnwindInfosHash = XXHash128.ComputeHash(unwindInfosBytes); + + innerHeader.SetHeaderHash(); + + stream.Seek(0L, SeekOrigin.Begin); + SerializeStructure(stream, innerHeader); + + translatedFuncsCount = GetEntriesCount(); + + ResetCarriersIfNeeded(); + + using (FileStream compressedStream = new(fileName, FileMode.OpenOrCreate)) + using (DeflateStream deflateStream = new(compressedStream, SaveCompressionLevel, true)) + { + try + { + SerializeStructure(compressedStream, outerHeader); + + stream.Seek(0L, SeekOrigin.Begin); + stream.CopyTo(deflateStream); + } + catch + { + compressedStream.Position = 0L; + } + + if (compressedStream.Position < compressedStream.Length) + { + compressedStream.SetLength(compressedStream.Position); + } + } + } + } + finally + { + if (intPtr != IntPtr.Zero) + { + Marshal.FreeHGlobal(intPtr); + } + } + + long fileSize = new FileInfo(fileName).Length; + + if (fileSize != 0L) + { + Logger.Info?.Print(LogClass.Ptc, $"Saved Translation Cache (size: {fileSize} bytes, translated functions: {translatedFuncsCount})."); + } + } + + public void LoadTranslations(Translator translator) + { + if (AreCarriersEmpty()) + { + return; + } + + long infosStreamLength = _infosStream.Length; + long relocsStreamLength = _relocsStream.Length; + long unwindInfosStreamLength = _unwindInfosStream.Length; + + _infosStream.Seek(0L, SeekOrigin.Begin); + _relocsStream.Seek(0L, SeekOrigin.Begin); + _unwindInfosStream.Seek(0L, SeekOrigin.Begin); + + using (BinaryReader relocsReader = new(_relocsStream, EncodingCache.UTF8NoBOM, true)) + using (BinaryReader unwindInfosReader = new(_unwindInfosStream, EncodingCache.UTF8NoBOM, true)) + { + for (int index = 0; index < GetEntriesCount(); index++) + { + InfoEntry infoEntry = DeserializeStructure(_infosStream); + + if (infoEntry.Stubbed) + { + SkipCode(index, infoEntry.CodeLength); + SkipReloc(infoEntry.RelocEntriesCount); + SkipUnwindInfo(unwindInfosReader); + + continue; + } + + bool isEntryChanged = infoEntry.Hash != ComputeHash(translator.Memory, infoEntry.Address, infoEntry.GuestSize); + + if (isEntryChanged || (!infoEntry.HighCq && Profiler.ProfiledFuncs.TryGetValue(infoEntry.Address, out var value) && value.HighCq)) + { + infoEntry.Stubbed = true; + infoEntry.CodeLength = 0; + UpdateInfo(infoEntry); + + StubCode(index); + StubReloc(infoEntry.RelocEntriesCount); + StubUnwindInfo(unwindInfosReader); + + if (isEntryChanged) + { + Logger.Info?.Print(LogClass.Ptc, $"Invalidated translated function (address: 0x{infoEntry.Address:X16})"); + } + + continue; + } + + byte[] code = ReadCode(index, infoEntry.CodeLength); + + Counter callCounter = null; + + if (infoEntry.RelocEntriesCount != 0) + { + RelocEntry[] relocEntries = GetRelocEntries(relocsReader, infoEntry.RelocEntriesCount); + + PatchCode(translator, code, relocEntries, out callCounter); + } + + UnwindInfo unwindInfo = ReadUnwindInfo(unwindInfosReader); + + TranslatedFunction func = FastTranslate(code, callCounter, infoEntry.GuestSize, unwindInfo, infoEntry.HighCq); + + translator.RegisterFunction(infoEntry.Address, func); + + bool isAddressUnique = translator.Functions.TryAdd(infoEntry.Address, infoEntry.GuestSize, func); + + Debug.Assert(isAddressUnique, $"The address 0x{infoEntry.Address:X16} is not unique."); + } + } + + if (_infosStream.Length != infosStreamLength || _infosStream.Position != infosStreamLength || + _relocsStream.Length != relocsStreamLength || _relocsStream.Position != relocsStreamLength || + _unwindInfosStream.Length != unwindInfosStreamLength || _unwindInfosStream.Position != unwindInfosStreamLength) + { + throw new Exception("The length of a memory stream has changed, or its position has not reached or has exceeded its end."); + } + + Logger.Info?.Print(LogClass.Ptc, $"{translator.Functions.Count} translated functions loaded"); + } + + private int GetEntriesCount() + { + return _codesList.Count; + } + + [Conditional("DEBUG")] + private void SkipCode(int index, int codeLength) + { + Debug.Assert(_codesList[index].Length == 0); + Debug.Assert(codeLength == 0); + } + + private void SkipReloc(int relocEntriesCount) + { + _relocsStream.Seek(relocEntriesCount * RelocEntry.Stride, SeekOrigin.Current); + } + + private void SkipUnwindInfo(BinaryReader unwindInfosReader) + { + int pushEntriesLength = unwindInfosReader.ReadInt32(); + + _unwindInfosStream.Seek(pushEntriesLength * UnwindPushEntry.Stride + UnwindInfo.Stride, SeekOrigin.Current); + } + + private byte[] ReadCode(int index, int codeLength) + { + Debug.Assert(_codesList[index].Length == codeLength); + + return _codesList[index]; + } + + private RelocEntry[] GetRelocEntries(BinaryReader relocsReader, int relocEntriesCount) + { + RelocEntry[] relocEntries = new RelocEntry[relocEntriesCount]; + + for (int i = 0; i < relocEntriesCount; i++) + { + int position = relocsReader.ReadInt32(); + SymbolType type = (SymbolType)relocsReader.ReadByte(); + ulong value = relocsReader.ReadUInt64(); + + relocEntries[i] = new RelocEntry(position, new Symbol(type, value)); + } + + return relocEntries; + } + + private void PatchCode(Translator translator, Span code, RelocEntry[] relocEntries, out Counter callCounter) + { + callCounter = null; + + foreach (RelocEntry relocEntry in relocEntries) + { + IntPtr? imm = null; + Symbol symbol = relocEntry.Symbol; + + if (symbol.Type == SymbolType.FunctionTable) + { + ulong guestAddress = symbol.Value; + + if (translator.FunctionTable.IsValid(guestAddress)) + { + unsafe { imm = (IntPtr)Unsafe.AsPointer(ref translator.FunctionTable.GetValue(guestAddress)); } + } + } + else if (symbol.Type == SymbolType.DelegateTable) + { + int index = (int)symbol.Value; + + if (Delegates.TryGetDelegateFuncPtrByIndex(index, out IntPtr funcPtr)) + { + imm = funcPtr; + } + } + else if (symbol == PageTableSymbol) + { + imm = translator.Memory.PageTablePointer; + } + else if (symbol == CountTableSymbol) + { + if (callCounter == null) + { + callCounter = new Counter(translator.CountTable); + } + + unsafe { imm = (IntPtr)Unsafe.AsPointer(ref callCounter.Value); } + } + else if (symbol == DispatchStubSymbol) + { + imm = translator.Stubs.DispatchStub; + } + + if (imm == null) + { + throw new Exception($"Unexpected reloc entry {relocEntry}."); + } + + BinaryPrimitives.WriteUInt64LittleEndian(code.Slice(relocEntry.Position, 8), (ulong)imm.Value); + } + } + + private UnwindInfo ReadUnwindInfo(BinaryReader unwindInfosReader) + { + int pushEntriesLength = unwindInfosReader.ReadInt32(); + + UnwindPushEntry[] pushEntries = new UnwindPushEntry[pushEntriesLength]; + + for (int i = 0; i < pushEntriesLength; i++) + { + int pseudoOp = unwindInfosReader.ReadInt32(); + int prologOffset = unwindInfosReader.ReadInt32(); + int regIndex = unwindInfosReader.ReadInt32(); + int stackOffsetOrAllocSize = unwindInfosReader.ReadInt32(); + + pushEntries[i] = new UnwindPushEntry((UnwindPseudoOp)pseudoOp, prologOffset, regIndex, stackOffsetOrAllocSize); + } + + int prologueSize = unwindInfosReader.ReadInt32(); + + return new UnwindInfo(pushEntries, prologueSize); + } + + private TranslatedFunction FastTranslate( + byte[] code, + Counter callCounter, + ulong guestSize, + UnwindInfo unwindInfo, + bool highCq) + { + var cFunc = new CompiledFunction(code, unwindInfo, RelocInfo.Empty); + var gFunc = cFunc.MapWithPointer(out IntPtr gFuncPointer); + + return new TranslatedFunction(gFunc, gFuncPointer, callCounter, guestSize, highCq); + } + + private void UpdateInfo(InfoEntry infoEntry) + { + _infosStream.Seek(-Unsafe.SizeOf(), SeekOrigin.Current); + + SerializeStructure(_infosStream, infoEntry); + } + + private void StubCode(int index) + { + _codesList[index] = Array.Empty(); + } + + private void StubReloc(int relocEntriesCount) + { + for (int i = 0; i < relocEntriesCount * RelocEntry.Stride; i++) + { + _relocsStream.WriteByte(FillingByte); + } + } + + private void StubUnwindInfo(BinaryReader unwindInfosReader) + { + int pushEntriesLength = unwindInfosReader.ReadInt32(); + + for (int i = 0; i < pushEntriesLength * UnwindPushEntry.Stride + UnwindInfo.Stride; i++) + { + _unwindInfosStream.WriteByte(FillingByte); + } + } + + public void MakeAndSaveTranslations(Translator translator) + { + var profiledFuncsToTranslate = Profiler.GetProfiledFuncsToTranslate(translator.Functions); + + _translateCount = 0; + _translateTotalCount = profiledFuncsToTranslate.Count; + + if (_translateTotalCount == 0) + { + ResetCarriersIfNeeded(); + + GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; + + return; + } + + int degreeOfParallelism = Environment.ProcessorCount; + + // If there are enough cores lying around, we leave one alone for other tasks. + if (degreeOfParallelism > 4) + { + degreeOfParallelism--; + } + + Logger.Info?.Print(LogClass.Ptc, $"{_translateCount} of {_translateTotalCount} functions translated | Thread count: {degreeOfParallelism}"); + + PtcStateChanged?.Invoke(PtcLoadingState.Start, _translateCount, _translateTotalCount); + + using AutoResetEvent progressReportEvent = new AutoResetEvent(false); + + Thread progressReportThread = new Thread(ReportProgress) + { + Name = "Ptc.ProgressReporter", + Priority = ThreadPriority.Lowest, + IsBackground = true + }; + + progressReportThread.Start(progressReportEvent); + + void TranslateFuncs() + { + while (profiledFuncsToTranslate.TryDequeue(out var item)) + { + ulong address = item.address; + + Debug.Assert(Profiler.IsAddressInStaticCodeRange(address)); + + TranslatedFunction func = translator.Translate(address, item.funcProfile.Mode, item.funcProfile.HighCq); + + bool isAddressUnique = translator.Functions.TryAdd(address, func.GuestSize, func); + + Debug.Assert(isAddressUnique, $"The address 0x{address:X16} is not unique."); + + Interlocked.Increment(ref _translateCount); + + translator.RegisterFunction(address, func); + + if (State != PtcState.Enabled) + { + break; + } + } + } + + List threads = new List(); + + for (int i = 0; i < degreeOfParallelism; i++) + { + Thread thread = new Thread(TranslateFuncs); + thread.IsBackground = true; + + threads.Add(thread); + } + + Stopwatch sw = Stopwatch.StartNew(); + + threads.ForEach((thread) => thread.Start()); + threads.ForEach((thread) => thread.Join()); + + threads.Clear(); + + progressReportEvent.Set(); + progressReportThread.Join(); + + sw.Stop(); + + PtcStateChanged?.Invoke(PtcLoadingState.Loaded, _translateCount, _translateTotalCount); + + Logger.Info?.Print(LogClass.Ptc, $"{_translateCount} of {_translateTotalCount} functions translated | Thread count: {degreeOfParallelism} in {sw.Elapsed.TotalSeconds} s"); + + Thread preSaveThread = new Thread(PreSave); + preSaveThread.IsBackground = true; + preSaveThread.Start(); + } + + private void ReportProgress(object state) + { + const int refreshRate = 50; // ms. + + AutoResetEvent endEvent = (AutoResetEvent)state; + + int count = 0; + + do + { + int newCount = _translateCount; + + if (count != newCount) + { + PtcStateChanged?.Invoke(PtcLoadingState.Loading, newCount, _translateTotalCount); + count = newCount; + } + } + while (!endEvent.WaitOne(refreshRate)); + } + + public static Hash128 ComputeHash(IMemoryManager memory, ulong address, ulong guestSize) + { + return XXHash128.ComputeHash(memory.GetSpan(address, checked((int)(guestSize)))); + } + + public void WriteCompiledFunction(ulong address, ulong guestSize, Hash128 hash, bool highCq, CompiledFunction compiledFunc) + { + lock (_lock) + { + byte[] code = compiledFunc.Code; + RelocInfo relocInfo = compiledFunc.RelocInfo; + UnwindInfo unwindInfo = compiledFunc.UnwindInfo; + + InfoEntry infoEntry = new InfoEntry(); + + infoEntry.Address = address; + infoEntry.GuestSize = guestSize; + infoEntry.Hash = hash; + infoEntry.HighCq = highCq; + infoEntry.Stubbed = false; + infoEntry.CodeLength = code.Length; + infoEntry.RelocEntriesCount = relocInfo.Entries.Length; + + SerializeStructure(_infosStream, infoEntry); + + WriteCode(code.AsSpan()); + + // WriteReloc. + using var relocInfoWriter = new BinaryWriter(_relocsStream, EncodingCache.UTF8NoBOM, true); + + foreach (RelocEntry entry in relocInfo.Entries) + { + relocInfoWriter.Write(entry.Position); + relocInfoWriter.Write((byte)entry.Symbol.Type); + relocInfoWriter.Write(entry.Symbol.Value); + } + + // WriteUnwindInfo. + using var unwindInfoWriter = new BinaryWriter(_unwindInfosStream, EncodingCache.UTF8NoBOM, true); + + unwindInfoWriter.Write(unwindInfo.PushEntries.Length); + + foreach (UnwindPushEntry unwindPushEntry in unwindInfo.PushEntries) + { + unwindInfoWriter.Write((int)unwindPushEntry.PseudoOp); + unwindInfoWriter.Write(unwindPushEntry.PrologOffset); + unwindInfoWriter.Write(unwindPushEntry.RegIndex); + unwindInfoWriter.Write(unwindPushEntry.StackOffsetOrAllocSize); + } + + unwindInfoWriter.Write(unwindInfo.PrologSize); + } + } + + private void WriteCode(ReadOnlySpan code) + { + _codesList.Add(code.ToArray()); + } + + public static bool GetEndianness() + { + return BitConverter.IsLittleEndian; + } + + private static FeatureInfo GetFeatureInfo() + { + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + return new FeatureInfo( + (ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap, + (ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap2, + (ulong)Arm64HardwareCapabilities.MacOsFeatureInfo, + 0, + 0); + } + else if (RuntimeInformation.ProcessArchitecture == Architecture.X64) + { + return new FeatureInfo( + (ulong)X86HardwareCapabilities.FeatureInfo1Ecx, + (ulong)X86HardwareCapabilities.FeatureInfo1Edx, + (ulong)X86HardwareCapabilities.FeatureInfo7Ebx, + (ulong)X86HardwareCapabilities.FeatureInfo7Ecx, + (ulong)X86HardwareCapabilities.Xcr0InfoEax); + } + else + { + return new FeatureInfo(0, 0, 0, 0, 0); + } + } + + private byte GetMemoryManagerMode() + { + return (byte)_memoryMode; + } + + private static uint GetOSPlatform() + { + uint osPlatform = 0u; + + osPlatform |= (OperatingSystem.IsFreeBSD() ? 1u : 0u) << 0; + osPlatform |= (OperatingSystem.IsLinux() ? 1u : 0u) << 1; + osPlatform |= (OperatingSystem.IsMacOS() ? 1u : 0u) << 2; + osPlatform |= (OperatingSystem.IsWindows() ? 1u : 0u) << 3; + + return osPlatform; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 86*/)] + private struct OuterHeader + { + public ulong Magic; + + public uint CacheFileVersion; + + public bool Endianness; + public FeatureInfo FeatureInfo; + public byte MemoryManagerMode; + public uint OSPlatform; + public uint Architecture; + + public long UncompressedStreamSize; + + public Hash128 HeaderHash; + + public void SetHeaderHash() + { + Span spanHeader = MemoryMarshal.CreateSpan(ref this, 1); + + HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader).Slice(0, Unsafe.SizeOf() - Unsafe.SizeOf())); + } + + public bool IsHeaderValid() + { + Span spanHeader = MemoryMarshal.CreateSpan(ref this, 1); + + return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader).Slice(0, Unsafe.SizeOf() - Unsafe.SizeOf())) == HeaderHash; + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 40*/)] + private record struct FeatureInfo(ulong FeatureInfo0, ulong FeatureInfo1, ulong FeatureInfo2, ulong FeatureInfo3, ulong FeatureInfo4); + + [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 128*/)] + private struct InnerHeader + { + public ulong Magic; + + public int InfosLength; + public long CodesLength; + public int RelocsLength; + public int UnwindInfosLength; + + public Hash128 InfosHash; + public Hash128 CodesHash; + public Hash128 RelocsHash; + public Hash128 UnwindInfosHash; + + public Hash128 HeaderHash; + + public void SetHeaderHash() + { + Span spanHeader = MemoryMarshal.CreateSpan(ref this, 1); + + HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader).Slice(0, Unsafe.SizeOf() - Unsafe.SizeOf())); + } + + public bool IsHeaderValid() + { + Span spanHeader = MemoryMarshal.CreateSpan(ref this, 1); + + return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader).Slice(0, Unsafe.SizeOf() - Unsafe.SizeOf())) == HeaderHash; + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 42*/)] + private struct InfoEntry + { + public ulong Address; + public ulong GuestSize; + public Hash128 Hash; + public bool HighCq; + public bool Stubbed; + public int CodeLength; + public int RelocEntriesCount; + } + + private void Enable() + { + State = PtcState.Enabled; + } + + public void Continue() + { + if (State == PtcState.Enabled) + { + State = PtcState.Continuing; + } + } + + public void Close() + { + if (State == PtcState.Enabled || + State == PtcState.Continuing) + { + State = PtcState.Closing; + } + } + + public void Disable() + { + State = PtcState.Disabled; + } + + private void Wait() + { + _waitEvent.WaitOne(); + } + + public void Dispose() + { + if (!_disposed) + { + _disposed = true; + + Wait(); + _waitEvent.Dispose(); + + DisposeCarriers(); + } + } + } +} diff --git a/src/ARMeilleure/Translation/PTC/PtcFormatter.cs b/src/ARMeilleure/Translation/PTC/PtcFormatter.cs new file mode 100644 index 00000000..2f7a9c21 --- /dev/null +++ b/src/ARMeilleure/Translation/PTC/PtcFormatter.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace ARMeilleure.Translation.PTC +{ + static class PtcFormatter + { + #region "Deserialize" + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Dictionary DeserializeDictionary(Stream stream, Func valueFunc) where TKey : struct + { + Dictionary dictionary = new(); + + int count = DeserializeStructure(stream); + + for (int i = 0; i < count; i++) + { + TKey key = DeserializeStructure(stream); + TValue value = valueFunc(stream); + + dictionary.Add(key, value); + } + + return dictionary; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static List DeserializeList(Stream stream) where T : struct + { + List list = new(); + + int count = DeserializeStructure(stream); + + for (int i = 0; i < count; i++) + { + T item = DeserializeStructure(stream); + + list.Add(item); + } + + return list; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T DeserializeStructure(Stream stream) where T : struct + { + T structure = default(T); + + Span spanT = MemoryMarshal.CreateSpan(ref structure, 1); + int bytesCount = stream.Read(MemoryMarshal.AsBytes(spanT)); + + if (bytesCount != Unsafe.SizeOf()) + { + throw new EndOfStreamException(); + } + + return structure; + } + #endregion + + #region "GetSerializeSize" + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetSerializeSizeDictionary(Dictionary dictionary, Func valueFunc) where TKey : struct + { + int size = 0; + + size += Unsafe.SizeOf(); + + foreach ((_, TValue value) in dictionary) + { + size += Unsafe.SizeOf(); + size += valueFunc(value); + } + + return size; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetSerializeSizeList(List list) where T : struct + { + int size = 0; + + size += Unsafe.SizeOf(); + + size += list.Count * Unsafe.SizeOf(); + + return size; + } + #endregion + + #region "Serialize" + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SerializeDictionary(Stream stream, Dictionary dictionary, Action valueAction) where TKey : struct + { + SerializeStructure(stream, dictionary.Count); + + foreach ((TKey key, TValue value) in dictionary) + { + SerializeStructure(stream, key); + valueAction(stream, value); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SerializeList(Stream stream, List list) where T : struct + { + SerializeStructure(stream, list.Count); + + foreach (T item in list) + { + SerializeStructure(stream, item); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SerializeStructure(Stream stream, T structure) where T : struct + { + Span spanT = MemoryMarshal.CreateSpan(ref structure, 1); + stream.Write(MemoryMarshal.AsBytes(spanT)); + } + #endregion + + #region "Extension methods" + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadFrom(this List list, Stream stream) where T : struct + { + int count = DeserializeStructure(stream); + + for (int i = 0; i < count; i++) + { + int itemLength = DeserializeStructure(stream); + + T[] item = new T[itemLength]; + + int bytesCount = stream.Read(MemoryMarshal.AsBytes(item.AsSpan())); + + if (bytesCount != itemLength) + { + throw new EndOfStreamException(); + } + + list.Add(item); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long Length(this List list) where T : struct + { + long size = 0L; + + size += Unsafe.SizeOf(); + + foreach (T[] item in list) + { + size += Unsafe.SizeOf(); + size += item.Length; + } + + return size; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteTo(this List list, Stream stream) where T : struct + { + SerializeStructure(stream, list.Count); + + foreach (T[] item in list) + { + SerializeStructure(stream, item.Length); + + stream.Write(MemoryMarshal.AsBytes(item.AsSpan())); + } + } + #endregion + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/PTC/PtcLoadingState.cs b/src/ARMeilleure/Translation/PTC/PtcLoadingState.cs new file mode 100644 index 00000000..526cf91f --- /dev/null +++ b/src/ARMeilleure/Translation/PTC/PtcLoadingState.cs @@ -0,0 +1,9 @@ +namespace ARMeilleure.Translation.PTC +{ + public enum PtcLoadingState + { + Start, + Loading, + Loaded + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/PTC/PtcProfiler.cs b/src/ARMeilleure/Translation/PTC/PtcProfiler.cs new file mode 100644 index 00000000..391e29c7 --- /dev/null +++ b/src/ARMeilleure/Translation/PTC/PtcProfiler.cs @@ -0,0 +1,421 @@ +using ARMeilleure.State; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; +using System; +using System.Buffers.Binary; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +using static ARMeilleure.Translation.PTC.PtcFormatter; + +namespace ARMeilleure.Translation.PTC +{ + class PtcProfiler + { + private const string OuterHeaderMagicString = "Pohd\0\0\0\0"; + + private const uint InternalVersion = 1866; //! Not to be incremented manually for each change to the ARMeilleure project. + + private const int SaveInterval = 30; // Seconds. + + private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest; + + private readonly Ptc _ptc; + + private readonly System.Timers.Timer _timer; + + private readonly ulong _outerHeaderMagic; + + private readonly ManualResetEvent _waitEvent; + + private readonly object _lock; + + private bool _disposed; + + private Hash128 _lastHash; + + public Dictionary ProfiledFuncs { get; private set; } + + public bool Enabled { get; private set; } + + public ulong StaticCodeStart { get; set; } + public ulong StaticCodeSize { get; set; } + + public PtcProfiler(Ptc ptc) + { + _ptc = ptc; + + _timer = new System.Timers.Timer((double)SaveInterval * 1000d); + _timer.Elapsed += PreSave; + + _outerHeaderMagic = BinaryPrimitives.ReadUInt64LittleEndian(EncodingCache.UTF8NoBOM.GetBytes(OuterHeaderMagicString).AsSpan()); + + _waitEvent = new ManualResetEvent(true); + + _lock = new object(); + + _disposed = false; + + ProfiledFuncs = new Dictionary(); + + Enabled = false; + } + + public void AddEntry(ulong address, ExecutionMode mode, bool highCq) + { + if (IsAddressInStaticCodeRange(address)) + { + Debug.Assert(!highCq); + + lock (_lock) + { + ProfiledFuncs.TryAdd(address, new FuncProfile(mode, highCq: false)); + } + } + } + + public void UpdateEntry(ulong address, ExecutionMode mode, bool highCq) + { + if (IsAddressInStaticCodeRange(address)) + { + Debug.Assert(highCq); + + lock (_lock) + { + Debug.Assert(ProfiledFuncs.ContainsKey(address)); + + ProfiledFuncs[address] = new FuncProfile(mode, highCq: true); + } + } + } + + public bool IsAddressInStaticCodeRange(ulong address) + { + return address >= StaticCodeStart && address < StaticCodeStart + StaticCodeSize; + } + + public ConcurrentQueue<(ulong address, FuncProfile funcProfile)> GetProfiledFuncsToTranslate(TranslatorCache funcs) + { + var profiledFuncsToTranslate = new ConcurrentQueue<(ulong address, FuncProfile funcProfile)>(); + + foreach (var profiledFunc in ProfiledFuncs) + { + if (!funcs.ContainsKey(profiledFunc.Key)) + { + profiledFuncsToTranslate.Enqueue((profiledFunc.Key, profiledFunc.Value)); + } + } + + return profiledFuncsToTranslate; + } + + public void ClearEntries() + { + ProfiledFuncs.Clear(); + ProfiledFuncs.TrimExcess(); + } + + public void PreLoad() + { + _lastHash = default; + + string fileNameActual = $"{_ptc.CachePathActual}.info"; + string fileNameBackup = $"{_ptc.CachePathBackup}.info"; + + FileInfo fileInfoActual = new FileInfo(fileNameActual); + FileInfo fileInfoBackup = new FileInfo(fileNameBackup); + + if (fileInfoActual.Exists && fileInfoActual.Length != 0L) + { + if (!Load(fileNameActual, false)) + { + if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L) + { + Load(fileNameBackup, true); + } + } + } + else if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L) + { + Load(fileNameBackup, true); + } + } + + private bool Load(string fileName, bool isBackup) + { + using (FileStream compressedStream = new(fileName, FileMode.Open)) + using (DeflateStream deflateStream = new(compressedStream, CompressionMode.Decompress, true)) + { + OuterHeader outerHeader = DeserializeStructure(compressedStream); + + if (!outerHeader.IsHeaderValid()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.Magic != _outerHeaderMagic) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.InfoFileVersion != InternalVersion) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + if (outerHeader.Endianness != Ptc.GetEndianness()) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + using (MemoryStream stream = MemoryStreamManager.Shared.GetStream()) + { + Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L); + + try + { + deflateStream.CopyTo(stream); + } + catch + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + Debug.Assert(stream.Position == stream.Length); + + stream.Seek(0L, SeekOrigin.Begin); + + Hash128 expectedHash = DeserializeStructure(stream); + + Hash128 actualHash = XXHash128.ComputeHash(GetReadOnlySpan(stream)); + + if (actualHash != expectedHash) + { + InvalidateCompressedStream(compressedStream); + + return false; + } + + ProfiledFuncs = Deserialize(stream); + + Debug.Assert(stream.Position == stream.Length); + + _lastHash = actualHash; + } + } + + long fileSize = new FileInfo(fileName).Length; + + Logger.Info?.Print(LogClass.Ptc, $"{(isBackup ? "Loaded Backup Profiling Info" : "Loaded Profiling Info")} (size: {fileSize} bytes, profiled functions: {ProfiledFuncs.Count})."); + + return true; + } + + private static Dictionary Deserialize(Stream stream) + { + return DeserializeDictionary(stream, (stream) => DeserializeStructure(stream)); + } + + private ReadOnlySpan GetReadOnlySpan(MemoryStream memoryStream) + { + return new(memoryStream.GetBuffer(), (int)memoryStream.Position, (int)memoryStream.Length - (int)memoryStream.Position); + } + + private void InvalidateCompressedStream(FileStream compressedStream) + { + compressedStream.SetLength(0L); + } + + private void PreSave(object source, System.Timers.ElapsedEventArgs e) + { + _waitEvent.Reset(); + + string fileNameActual = $"{_ptc.CachePathActual}.info"; + string fileNameBackup = $"{_ptc.CachePathBackup}.info"; + + FileInfo fileInfoActual = new FileInfo(fileNameActual); + + if (fileInfoActual.Exists && fileInfoActual.Length != 0L) + { + File.Copy(fileNameActual, fileNameBackup, true); + } + + Save(fileNameActual); + + _waitEvent.Set(); + } + + private void Save(string fileName) + { + int profiledFuncsCount; + + OuterHeader outerHeader = new OuterHeader(); + + outerHeader.Magic = _outerHeaderMagic; + + outerHeader.InfoFileVersion = InternalVersion; + outerHeader.Endianness = Ptc.GetEndianness(); + + outerHeader.SetHeaderHash(); + + using (MemoryStream stream = MemoryStreamManager.Shared.GetStream()) + { + Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L); + + stream.Seek((long)Unsafe.SizeOf(), SeekOrigin.Begin); + + lock (_lock) + { + Serialize(stream, ProfiledFuncs); + + profiledFuncsCount = ProfiledFuncs.Count; + } + + Debug.Assert(stream.Position == stream.Length); + + stream.Seek((long)Unsafe.SizeOf(), SeekOrigin.Begin); + Hash128 hash = XXHash128.ComputeHash(GetReadOnlySpan(stream)); + + stream.Seek(0L, SeekOrigin.Begin); + SerializeStructure(stream, hash); + + if (hash == _lastHash) + { + return; + } + + using (FileStream compressedStream = new(fileName, FileMode.OpenOrCreate)) + using (DeflateStream deflateStream = new(compressedStream, SaveCompressionLevel, true)) + { + try + { + SerializeStructure(compressedStream, outerHeader); + + stream.WriteTo(deflateStream); + + _lastHash = hash; + } + catch + { + compressedStream.Position = 0L; + + _lastHash = default; + } + + if (compressedStream.Position < compressedStream.Length) + { + compressedStream.SetLength(compressedStream.Position); + } + } + } + + long fileSize = new FileInfo(fileName).Length; + + if (fileSize != 0L) + { + Logger.Info?.Print(LogClass.Ptc, $"Saved Profiling Info (size: {fileSize} bytes, profiled functions: {profiledFuncsCount})."); + } + } + + private void Serialize(Stream stream, Dictionary profiledFuncs) + { + SerializeDictionary(stream, profiledFuncs, (stream, structure) => SerializeStructure(stream, structure)); + } + + [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 29*/)] + private struct OuterHeader + { + public ulong Magic; + + public uint InfoFileVersion; + + public bool Endianness; + + public Hash128 HeaderHash; + + public void SetHeaderHash() + { + Span spanHeader = MemoryMarshal.CreateSpan(ref this, 1); + + HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader).Slice(0, Unsafe.SizeOf() - Unsafe.SizeOf())); + } + + public bool IsHeaderValid() + { + Span spanHeader = MemoryMarshal.CreateSpan(ref this, 1); + + return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader).Slice(0, Unsafe.SizeOf() - Unsafe.SizeOf())) == HeaderHash; + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 5*/)] + public struct FuncProfile + { + public ExecutionMode Mode; + public bool HighCq; + + public FuncProfile(ExecutionMode mode, bool highCq) + { + Mode = mode; + HighCq = highCq; + } + } + + public void Start() + { + if (_ptc.State == PtcState.Enabled || + _ptc.State == PtcState.Continuing) + { + Enabled = true; + + _timer.Enabled = true; + } + } + + public void Stop() + { + Enabled = false; + + if (!_disposed) + { + _timer.Enabled = false; + } + } + + public void Wait() + { + _waitEvent.WaitOne(); + } + + public void Dispose() + { + if (!_disposed) + { + _disposed = true; + + _timer.Elapsed -= PreSave; + _timer.Dispose(); + + Wait(); + _waitEvent.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/PTC/PtcState.cs b/src/ARMeilleure/Translation/PTC/PtcState.cs new file mode 100644 index 00000000..ca4f4108 --- /dev/null +++ b/src/ARMeilleure/Translation/PTC/PtcState.cs @@ -0,0 +1,10 @@ +namespace ARMeilleure.Translation.PTC +{ + enum PtcState + { + Enabled, + Continuing, + Closing, + Disabled + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/RegisterToLocal.cs b/src/ARMeilleure/Translation/RegisterToLocal.cs new file mode 100644 index 00000000..abb9b373 --- /dev/null +++ b/src/ARMeilleure/Translation/RegisterToLocal.cs @@ -0,0 +1,52 @@ +using ARMeilleure.IntermediateRepresentation; +using System.Collections.Generic; + +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Translation +{ + static class RegisterToLocal + { + public static void Rename(ControlFlowGraph cfg) + { + Dictionary registerToLocalMap = new Dictionary(); + + Operand GetLocal(Operand op) + { + Register register = op.GetRegister(); + + if (!registerToLocalMap.TryGetValue(register, out Operand local)) + { + local = Local(op.Type); + + registerToLocalMap.Add(register, local); + } + + return local; + } + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + Operand dest = node.Destination; + + if (dest != default && dest.Kind == OperandKind.Register) + { + node.Destination = GetLocal(dest); + } + + for (int index = 0; index < node.SourcesCount; index++) + { + Operand source = node.GetSource(index); + + if (source.Kind == OperandKind.Register) + { + node.SetSource(index, GetLocal(source)); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/RegisterUsage.cs b/src/ARMeilleure/Translation/RegisterUsage.cs new file mode 100644 index 00000000..3ec0a7b4 --- /dev/null +++ b/src/ARMeilleure/Translation/RegisterUsage.cs @@ -0,0 +1,394 @@ +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using System; +using System.Numerics; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.Translation +{ + static class RegisterUsage + { + private const int RegsCount = 32; + private const int RegsMask = RegsCount - 1; + + private readonly struct RegisterMask : IEquatable + { + public long IntMask => Mask.GetElement(0); + public long VecMask => Mask.GetElement(1); + + public Vector128 Mask { get; } + + public RegisterMask(Vector128 mask) + { + Mask = mask; + } + + public RegisterMask(long intMask, long vecMask) + { + Mask = Vector128.Create(intMask, vecMask); + } + + public static RegisterMask operator &(RegisterMask x, RegisterMask y) + { + if (Sse2.IsSupported) + { + return new RegisterMask(Sse2.And(x.Mask, y.Mask)); + } + + return new RegisterMask(x.IntMask & y.IntMask, x.VecMask & y.VecMask); + } + + public static RegisterMask operator |(RegisterMask x, RegisterMask y) + { + if (Sse2.IsSupported) + { + return new RegisterMask(Sse2.Or(x.Mask, y.Mask)); + } + + return new RegisterMask(x.IntMask | y.IntMask, x.VecMask | y.VecMask); + } + + public static RegisterMask operator ~(RegisterMask x) + { + if (Sse2.IsSupported) + { + return new RegisterMask(Sse2.AndNot(x.Mask, Vector128.AllBitsSet)); + } + + return new RegisterMask(~x.IntMask, ~x.VecMask); + } + + public static bool operator ==(RegisterMask x, RegisterMask y) + { + return x.Equals(y); + } + + public static bool operator !=(RegisterMask x, RegisterMask y) + { + return !x.Equals(y); + } + + public override bool Equals(object obj) + { + return obj is RegisterMask regMask && Equals(regMask); + } + + public bool Equals(RegisterMask other) + { + return Mask.Equals(other.Mask); + } + + public override int GetHashCode() + { + return Mask.GetHashCode(); + } + } + + public static void RunPass(ControlFlowGraph cfg, ExecutionMode mode) + { + // Compute local register inputs and outputs used inside blocks. + RegisterMask[] localInputs = new RegisterMask[cfg.Blocks.Count]; + RegisterMask[] localOutputs = new RegisterMask[cfg.Blocks.Count]; + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + for (int index = 0; index < node.SourcesCount; index++) + { + Operand source = node.GetSource(index); + + if (source.Kind == OperandKind.Register) + { + Register register = source.GetRegister(); + + localInputs[block.Index] |= GetMask(register) & ~localOutputs[block.Index]; + } + } + + if (node.Destination != default && node.Destination.Kind == OperandKind.Register) + { + localOutputs[block.Index] |= GetMask(node.Destination.GetRegister()); + } + } + } + + // Compute global register inputs and outputs used across blocks. + RegisterMask[] globalCmnOutputs = new RegisterMask[cfg.Blocks.Count]; + + RegisterMask[] globalInputs = new RegisterMask[cfg.Blocks.Count]; + RegisterMask[] globalOutputs = new RegisterMask[cfg.Blocks.Count]; + + bool modified; + bool firstPass = true; + + do + { + modified = false; + + // Compute register outputs. + for (int index = cfg.PostOrderBlocks.Length - 1; index >= 0; index--) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + if (block.Predecessors.Count != 0 && !HasContextLoad(block)) + { + BasicBlock predecessor = block.Predecessors[0]; + + RegisterMask cmnOutputs = localOutputs[predecessor.Index] | globalCmnOutputs[predecessor.Index]; + RegisterMask outputs = globalOutputs[predecessor.Index]; + + for (int pIndex = 1; pIndex < block.Predecessors.Count; pIndex++) + { + predecessor = block.Predecessors[pIndex]; + + cmnOutputs &= localOutputs[predecessor.Index] | globalCmnOutputs[predecessor.Index]; + outputs |= globalOutputs[predecessor.Index]; + } + + globalInputs[block.Index] |= outputs & ~cmnOutputs; + + if (!firstPass) + { + cmnOutputs &= globalCmnOutputs[block.Index]; + } + + modified |= Exchange(globalCmnOutputs, block.Index, cmnOutputs); + outputs |= localOutputs[block.Index]; + modified |= Exchange(globalOutputs, block.Index, globalOutputs[block.Index] | outputs); + } + else + { + modified |= Exchange(globalOutputs, block.Index, localOutputs[block.Index]); + } + } + + // Compute register inputs. + for (int index = 0; index < cfg.PostOrderBlocks.Length; index++) + { + BasicBlock block = cfg.PostOrderBlocks[index]; + + RegisterMask inputs = localInputs[block.Index]; + + for (int i = 0; i < block.SuccessorsCount; i++) + { + inputs |= globalInputs[block.GetSuccessor(i).Index]; + } + + inputs &= ~globalCmnOutputs[block.Index]; + + modified |= Exchange(globalInputs, block.Index, globalInputs[block.Index] | inputs); + } + + firstPass = false; + } + while (modified); + + // Insert load and store context instructions where needed. + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + bool hasContextLoad = HasContextLoad(block); + + if (hasContextLoad) + { + block.Operations.Remove(block.Operations.First); + } + + Operand arg = default; + + // The only block without any predecessor should be the entry block. + // It always needs a context load as it is the first block to run. + if (block.Predecessors.Count == 0 || hasContextLoad) + { + long vecMask = globalInputs[block.Index].VecMask; + long intMask = globalInputs[block.Index].IntMask; + + if (vecMask != 0 || intMask != 0) + { + arg = Local(OperandType.I64); + + Operation loadArg = block.Operations.AddFirst(Operation(Instruction.LoadArgument, arg, Const(0))); + + LoadLocals(block, vecMask, RegisterType.Vector, mode, loadArg, arg); + LoadLocals(block, intMask, RegisterType.Integer, mode, loadArg, arg); + } + } + + bool hasContextStore = HasContextStore(block); + + if (hasContextStore) + { + block.Operations.Remove(block.Operations.Last); + } + + if (EndsWithReturn(block) || hasContextStore) + { + long vecMask = globalOutputs[block.Index].VecMask; + long intMask = globalOutputs[block.Index].IntMask; + + if (vecMask != 0 || intMask != 0) + { + if (arg == default) + { + arg = Local(OperandType.I64); + + block.Append(Operation(Instruction.LoadArgument, arg, Const(0))); + } + + StoreLocals(block, intMask, RegisterType.Integer, mode, arg); + StoreLocals(block, vecMask, RegisterType.Vector, mode, arg); + } + } + } + } + + private static bool HasContextLoad(BasicBlock block) + { + return StartsWith(block, Instruction.LoadFromContext) && block.Operations.First.SourcesCount == 0; + } + + private static bool HasContextStore(BasicBlock block) + { + return EndsWith(block, Instruction.StoreToContext) && block.Operations.Last.SourcesCount == 0; + } + + private static bool StartsWith(BasicBlock block, Instruction inst) + { + if (block.Operations.Count > 0) + { + Operation first = block.Operations.First; + + return first != default && first.Instruction == inst; + } + + return false; + } + + private static bool EndsWith(BasicBlock block, Instruction inst) + { + if (block.Operations.Count > 0) + { + Operation last = block.Operations.Last; + + return last != default && last.Instruction == inst; + } + + return false; + } + + private static RegisterMask GetMask(Register register) + { + long intMask = 0; + long vecMask = 0; + + switch (register.Type) + { + case RegisterType.Flag: intMask = (1L << RegsCount) << register.Index; break; + case RegisterType.Integer: intMask = 1L << register.Index; break; + case RegisterType.FpFlag: vecMask = (1L << RegsCount) << register.Index; break; + case RegisterType.Vector: vecMask = 1L << register.Index; break; + } + + return new RegisterMask(intMask, vecMask); + } + + private static bool Exchange(RegisterMask[] masks, int blkIndex, RegisterMask value) + { + ref RegisterMask curValue = ref masks[blkIndex]; + + bool changed = curValue != value; + + curValue = value; + + return changed; + } + + private static void LoadLocals( + BasicBlock block, + long inputs, + RegisterType baseType, + ExecutionMode mode, + Operation loadArg, + Operand arg) + { + while (inputs != 0) + { + int bit = 63 - BitOperations.LeadingZeroCount((ulong)inputs); + + Operand dest = GetRegFromBit(bit, baseType, mode); + Operand offset = Const((long)NativeContext.GetRegisterOffset(dest.GetRegister())); + Operand addr = Local(OperandType.I64); + + block.Operations.AddAfter(loadArg, Operation(Instruction.Load, dest, addr)); + block.Operations.AddAfter(loadArg, Operation(Instruction.Add, addr, arg, offset)); + + inputs &= ~(1L << bit); + } + } + + private static void StoreLocals( + BasicBlock block, + long outputs, + RegisterType baseType, + ExecutionMode mode, + Operand arg) + { + while (outputs != 0) + { + int bit = BitOperations.TrailingZeroCount(outputs); + + Operand source = GetRegFromBit(bit, baseType, mode); + Operand offset = Const((long)NativeContext.GetRegisterOffset(source.GetRegister())); + Operand addr = Local(OperandType.I64); + + block.Append(Operation(Instruction.Add, addr, arg, offset)); + block.Append(Operation(Instruction.Store, default, addr, source)); + + outputs &= ~(1L << bit); + } + } + + private static Operand GetRegFromBit(int bit, RegisterType baseType, ExecutionMode mode) + { + if (bit < RegsCount) + { + return Register(bit, baseType, GetOperandType(baseType, mode)); + } + else if (baseType == RegisterType.Integer) + { + return Register(bit & RegsMask, RegisterType.Flag, OperandType.I32); + } + else if (baseType == RegisterType.Vector) + { + return Register(bit & RegsMask, RegisterType.FpFlag, OperandType.I32); + } + else + { + throw new ArgumentOutOfRangeException(nameof(bit)); + } + } + + private static OperandType GetOperandType(RegisterType type, ExecutionMode mode) + { + switch (type) + { + case RegisterType.Flag: return OperandType.I32; + case RegisterType.FpFlag: return OperandType.I32; + case RegisterType.Integer: return (mode == ExecutionMode.Aarch64) ? OperandType.I64 : OperandType.I32; + case RegisterType.Vector: return OperandType.V128; + } + + throw new ArgumentException($"Invalid register type \"{type}\"."); + } + + private static bool EndsWithReturn(BasicBlock block) + { + Operation last = block.Operations.Last; + + return last != default && last.Instruction == Instruction.Return; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/RejitRequest.cs b/src/ARMeilleure/Translation/RejitRequest.cs new file mode 100644 index 00000000..1bed5c0a --- /dev/null +++ b/src/ARMeilleure/Translation/RejitRequest.cs @@ -0,0 +1,16 @@ +using ARMeilleure.State; + +namespace ARMeilleure.Translation +{ + struct RejitRequest + { + public ulong Address; + public ExecutionMode Mode; + + public RejitRequest(ulong address, ExecutionMode mode) + { + Address = address; + Mode = mode; + } + } +} diff --git a/src/ARMeilleure/Translation/SsaConstruction.cs b/src/ARMeilleure/Translation/SsaConstruction.cs new file mode 100644 index 00000000..2b6efc11 --- /dev/null +++ b/src/ARMeilleure/Translation/SsaConstruction.cs @@ -0,0 +1,289 @@ +using ARMeilleure.Common; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Translation +{ + static partial class Ssa + { + private class DefMap + { + private readonly Dictionary _map; + private readonly BitMap _phiMasks; + + public DefMap() + { + _map = new Dictionary(); + _phiMasks = new BitMap(Allocators.Default, RegisterConsts.TotalCount); + } + + public bool TryAddOperand(int key, Operand operand) + { + return _map.TryAdd(key, operand); + } + + public bool TryGetOperand(int key, out Operand operand) + { + return _map.TryGetValue(key, out operand); + } + + public bool AddPhi(int key) + { + return _phiMasks.Set(key); + } + + public bool HasPhi(int key) + { + return _phiMasks.IsSet(key); + } + } + + public static void Construct(ControlFlowGraph cfg) + { + var globalDefs = new DefMap[cfg.Blocks.Count]; + var localDefs = new Operand[cfg.LocalsCount + RegisterConsts.TotalCount]; + + var dfPhiBlocks = new Queue(); + + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + globalDefs[block.Index] = new DefMap(); + } + + // First pass, get all defs and locals uses. + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + for (int index = 0; index < node.SourcesCount; index++) + { + Operand src = node.GetSource(index); + + if (TryGetId(src, out int srcKey)) + { + Operand local = localDefs[srcKey]; + + if (local == default) + { + local = src; + } + + node.SetSource(index, local); + } + } + + Operand dest = node.Destination; + + if (TryGetId(dest, out int destKey)) + { + Operand local = Local(dest.Type); + + localDefs[destKey] = local; + + node.Destination = local; + } + } + + for (int key = 0; key < localDefs.Length; key++) + { + Operand local = localDefs[key]; + + if (local == default) + { + continue; + } + + globalDefs[block.Index].TryAddOperand(key, local); + + dfPhiBlocks.Enqueue(block); + + while (dfPhiBlocks.TryDequeue(out BasicBlock dfPhiBlock)) + { + foreach (BasicBlock domFrontier in dfPhiBlock.DominanceFrontiers) + { + if (globalDefs[domFrontier.Index].AddPhi(key)) + { + dfPhiBlocks.Enqueue(domFrontier); + } + } + } + } + + Array.Clear(localDefs); + } + + // Second pass, rename variables with definitions on different blocks. + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + for (Operation node = block.Operations.First; node != default; node = node.ListNext) + { + for (int index = 0; index < node.SourcesCount; index++) + { + Operand src = node.GetSource(index); + + if (TryGetId(src, out int key)) + { + Operand local = localDefs[key]; + + if (local == default) + { + local = FindDef(globalDefs, block, src); + localDefs[key] = local; + } + + node.SetSource(index, local); + } + } + } + + Array.Clear(localDefs); + } + } + + private static Operand FindDef(DefMap[] globalDefs, BasicBlock current, Operand operand) + { + if (globalDefs[current.Index].HasPhi(GetId(operand))) + { + return InsertPhi(globalDefs, current, operand); + } + + if (current != current.ImmediateDominator) + { + return FindDefOnPred(globalDefs, current.ImmediateDominator, operand); + } + + return Undef(); + } + + private static Operand FindDefOnPred(DefMap[] globalDefs, BasicBlock current, Operand operand) + { + BasicBlock previous; + + do + { + DefMap defMap = globalDefs[current.Index]; + + int key = GetId(operand); + + if (defMap.TryGetOperand(key, out Operand lastDef)) + { + return lastDef; + } + + if (defMap.HasPhi(key)) + { + return InsertPhi(globalDefs, current, operand); + } + + previous = current; + current = current.ImmediateDominator; + } + while (previous != current); + + return Undef(); + } + + private static Operand InsertPhi(DefMap[] globalDefs, BasicBlock block, Operand operand) + { + // This block has a Phi that has not been materialized yet, but that + // would define a new version of the variable we're looking for. We need + // to materialize the Phi, add all the block/operand pairs into the Phi, and + // then use the definition from that Phi. + Operand local = Local(operand.Type); + + Operation operation = Operation.Factory.PhiOperation(local, block.Predecessors.Count); + + AddPhi(block, operation); + + globalDefs[block.Index].TryAddOperand(GetId(operand), local); + + PhiOperation phi = operation.AsPhi(); + + for (int index = 0; index < block.Predecessors.Count; index++) + { + BasicBlock predecessor = block.Predecessors[index]; + + phi.SetBlock(index, predecessor); + phi.SetSource(index, FindDefOnPred(globalDefs, predecessor, operand)); + } + + return local; + } + + private static void AddPhi(BasicBlock block, Operation phi) + { + Operation node = block.Operations.First; + + if (node != default) + { + while (node.ListNext != default && node.ListNext.Instruction == Instruction.Phi) + { + node = node.ListNext; + } + } + + if (node != default && node.Instruction == Instruction.Phi) + { + block.Operations.AddAfter(node, phi); + } + else + { + block.Operations.AddFirst(phi); + } + } + + private static bool TryGetId(Operand operand, out int result) + { + if (operand != default) + { + if (operand.Kind == OperandKind.Register) + { + Register reg = operand.GetRegister(); + + if (reg.Type == RegisterType.Integer) + { + result = reg.Index; + } + else if (reg.Type == RegisterType.Vector) + { + result = RegisterConsts.IntRegsCount + reg.Index; + } + else if (reg.Type == RegisterType.Flag) + { + result = RegisterConsts.IntAndVecRegsCount + reg.Index; + } + else /* if (reg.Type == RegisterType.FpFlag) */ + { + result = RegisterConsts.FpFlagsOffset + reg.Index; + } + + return true; + } + else if (operand.Kind == OperandKind.LocalVariable && operand.GetLocalNumber() > 0) + { + result = RegisterConsts.TotalCount + operand.GetLocalNumber() - 1; + + return true; + } + } + + result = -1; + + return false; + } + + private static int GetId(Operand operand) + { + if (!TryGetId(operand, out int key)) + { + Debug.Fail("OperandKind must be Register or a numbered LocalVariable."); + } + + return key; + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/SsaDeconstruction.cs b/src/ARMeilleure/Translation/SsaDeconstruction.cs new file mode 100644 index 00000000..cd6bcca1 --- /dev/null +++ b/src/ARMeilleure/Translation/SsaDeconstruction.cs @@ -0,0 +1,48 @@ +using ARMeilleure.IntermediateRepresentation; + +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; +using static ARMeilleure.IntermediateRepresentation.Operation.Factory; + +namespace ARMeilleure.Translation +{ + static partial class Ssa + { + public static void Deconstruct(ControlFlowGraph cfg) + { + for (BasicBlock block = cfg.Blocks.First; block != null; block = block.ListNext) + { + Operation operation = block.Operations.First; + + while (operation != default && operation.Instruction == Instruction.Phi) + { + Operation nextNode = operation.ListNext; + + Operand local = Local(operation.Destination.Type); + + PhiOperation phi = operation.AsPhi(); + + for (int index = 0; index < phi.SourcesCount; index++) + { + BasicBlock predecessor = phi.GetBlock(cfg, index); + + Operand source = phi.GetSource(index); + + predecessor.Append(Operation(Instruction.Copy, local, source)); + + phi.SetSource(index, default); + } + + Operation copyOp = Operation(Instruction.Copy, operation.Destination, local); + + block.Operations.AddBefore(operation, copyOp); + + operation.Destination = default; + + block.Operations.Remove(operation); + + operation = nextNode; + } + } + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/TranslatedFunction.cs b/src/ARMeilleure/Translation/TranslatedFunction.cs new file mode 100644 index 00000000..f007883e --- /dev/null +++ b/src/ARMeilleure/Translation/TranslatedFunction.cs @@ -0,0 +1,34 @@ +using ARMeilleure.Common; +using System; + +namespace ARMeilleure.Translation +{ + class TranslatedFunction + { + private readonly GuestFunction _func; // Ensure that this delegate will not be garbage collected. + + public IntPtr FuncPointer { get; } + public Counter CallCounter { get; } + public ulong GuestSize { get; } + public bool HighCq { get; } + + public TranslatedFunction(GuestFunction func, IntPtr funcPointer, Counter callCounter, ulong guestSize, bool highCq) + { + _func = func; + FuncPointer = funcPointer; + CallCounter = callCounter; + GuestSize = guestSize; + HighCq = highCq; + } + + public ulong Execute(State.ExecutionContext context) + { + return _func(context.NativeContextPtr); + } + + public ulong Execute(WrapperFunction dispatcher, State.ExecutionContext context) + { + return dispatcher(context.NativeContextPtr, (ulong)FuncPointer); + } + } +} \ No newline at end of file diff --git a/src/ARMeilleure/Translation/Translator.cs b/src/ARMeilleure/Translation/Translator.cs new file mode 100644 index 00000000..f349c5eb --- /dev/null +++ b/src/ARMeilleure/Translation/Translator.cs @@ -0,0 +1,576 @@ +using ARMeilleure.CodeGen; +using ARMeilleure.Common; +using ARMeilleure.Decoders; +using ARMeilleure.Diagnostics; +using ARMeilleure.Instructions; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.Memory; +using ARMeilleure.Signal; +using ARMeilleure.State; +using ARMeilleure.Translation.Cache; +using ARMeilleure.Translation.PTC; +using Ryujinx.Common; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Translation +{ + public class Translator + { + private static readonly AddressTable.Level[] Levels64Bit = + new AddressTable.Level[] + { + new(31, 17), + new(23, 8), + new(15, 8), + new( 7, 8), + new( 2, 5) + }; + + private static readonly AddressTable.Level[] Levels32Bit = + new AddressTable.Level[] + { + new(31, 17), + new(23, 8), + new(15, 8), + new( 7, 8), + new( 1, 6) + }; + + private readonly IJitMemoryAllocator _allocator; + private readonly ConcurrentQueue> _oldFuncs; + + private readonly Ptc _ptc; + + internal TranslatorCache Functions { get; } + internal AddressTable FunctionTable { get; } + internal EntryTable CountTable { get; } + internal TranslatorStubs Stubs { get; } + internal TranslatorQueue Queue { get; } + internal IMemoryManager Memory { get; } + + private volatile int _threadCount; + + // FIXME: Remove this once the init logic of the emulator will be redone. + public static readonly ManualResetEvent IsReadyForTranslation = new(false); + + public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, bool for64Bits) + { + _allocator = allocator; + Memory = memory; + + _oldFuncs = new ConcurrentQueue>(); + + _ptc = new Ptc(); + + Queue = new TranslatorQueue(); + + JitCache.Initialize(allocator); + + CountTable = new EntryTable(); + Functions = new TranslatorCache(); + FunctionTable = new AddressTable(for64Bits ? Levels64Bit : Levels32Bit); + Stubs = new TranslatorStubs(this); + + FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub; + + if (memory.Type.IsHostMapped()) + { + NativeSignalHandler.InitializeSignalHandler(allocator.GetPageSize()); + } + } + + public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled) + { + _ptc.Initialize(titleIdText, displayVersion, enabled, Memory.Type); + return _ptc; + } + + public void PrepareCodeRange(ulong address, ulong size) + { + if (_ptc.Profiler.StaticCodeSize == 0) + { + _ptc.Profiler.StaticCodeStart = address; + _ptc.Profiler.StaticCodeSize = size; + } + } + + public void Execute(State.ExecutionContext context, ulong address) + { + if (Interlocked.Increment(ref _threadCount) == 1) + { + IsReadyForTranslation.WaitOne(); + + if (_ptc.State == PtcState.Enabled) + { + Debug.Assert(Functions.Count == 0); + _ptc.LoadTranslations(this); + _ptc.MakeAndSaveTranslations(this); + } + + _ptc.Profiler.Start(); + + _ptc.Disable(); + + // Simple heuristic, should be user configurable in future. (1 for 4 core/ht or less, 2 for 6 core + ht + // etc). All threads are normal priority except from the last, which just fills as much of the last core + // as the os lets it with a low priority. If we only have one rejit thread, it should be normal priority + // as highCq code is performance critical. + // + // TODO: Use physical cores rather than logical. This only really makes sense for processors with + // hyperthreading. Requires OS specific code. + int unboundedThreadCount = Math.Max(1, (Environment.ProcessorCount - 6) / 3); + int threadCount = Math.Min(4, unboundedThreadCount); + + for (int i = 0; i < threadCount; i++) + { + bool last = i != 0 && i == unboundedThreadCount - 1; + + Thread backgroundTranslatorThread = new Thread(BackgroundTranslate) + { + Name = "CPU.BackgroundTranslatorThread." + i, + Priority = last ? ThreadPriority.Lowest : ThreadPriority.Normal + }; + + backgroundTranslatorThread.Start(); + } + } + + Statistics.InitializeTimer(); + + NativeInterface.RegisterThread(context, Memory, this); + + if (Optimizations.UseUnmanagedDispatchLoop) + { + Stubs.DispatchLoop(context.NativeContextPtr, address); + } + else + { + do + { + address = ExecuteSingle(context, address); + } + while (context.Running && address != 0); + } + + NativeInterface.UnregisterThread(); + + if (Interlocked.Decrement(ref _threadCount) == 0) + { + ClearJitCache(); + + Queue.Dispose(); + Stubs.Dispose(); + FunctionTable.Dispose(); + CountTable.Dispose(); + + _ptc.Close(); + _ptc.Profiler.Stop(); + + _ptc.Dispose(); + _ptc.Profiler.Dispose(); + } + } + + private ulong ExecuteSingle(State.ExecutionContext context, ulong address) + { + TranslatedFunction func = GetOrTranslate(address, context.ExecutionMode); + + Statistics.StartTimer(); + + ulong nextAddr = func.Execute(Stubs.ContextWrapper, context); + + Statistics.StopTimer(address); + + return nextAddr; + } + + public ulong Step(State.ExecutionContext context, ulong address) + { + TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true); + + address = func.Execute(Stubs.ContextWrapper, context); + + EnqueueForDeletion(address, func); + + return address; + } + + internal TranslatedFunction GetOrTranslate(ulong address, ExecutionMode mode) + { + if (!Functions.TryGetValue(address, out TranslatedFunction func)) + { + func = Translate(address, mode, highCq: false); + + TranslatedFunction oldFunc = Functions.GetOrAdd(address, func.GuestSize, func); + + if (oldFunc != func) + { + JitCache.Unmap(func.FuncPointer); + func = oldFunc; + } + + if (_ptc.Profiler.Enabled) + { + _ptc.Profiler.AddEntry(address, mode, highCq: false); + } + + RegisterFunction(address, func); + } + + return func; + } + + internal void RegisterFunction(ulong guestAddress, TranslatedFunction func) + { + if (FunctionTable.IsValid(guestAddress) && (Optimizations.AllowLcqInFunctionTable || func.HighCq)) + { + Volatile.Write(ref FunctionTable.GetValue(guestAddress), (ulong)func.FuncPointer); + } + } + + internal TranslatedFunction Translate(ulong address, ExecutionMode mode, bool highCq, bool singleStep = false) + { + var context = new ArmEmitterContext( + Memory, + CountTable, + FunctionTable, + Stubs, + address, + highCq, + _ptc.State != PtcState.Disabled, + mode: Aarch32Mode.User); + + Logger.StartPass(PassName.Decoding); + + Block[] blocks = Decoder.Decode(Memory, address, mode, highCq, singleStep ? DecoderMode.SingleInstruction : DecoderMode.MultipleBlocks); + + Logger.EndPass(PassName.Decoding); + + Logger.StartPass(PassName.Translation); + + EmitSynchronization(context); + + if (blocks[0].Address != address) + { + context.Branch(context.GetLabel(address)); + } + + ControlFlowGraph cfg = EmitAndGetCFG(context, blocks, out Range funcRange, out Counter counter); + + ulong funcSize = funcRange.End - funcRange.Start; + + Logger.EndPass(PassName.Translation, cfg); + + Logger.StartPass(PassName.RegisterUsage); + + RegisterUsage.RunPass(cfg, mode); + + Logger.EndPass(PassName.RegisterUsage); + + var retType = OperandType.I64; + var argTypes = new OperandType[] { OperandType.I64 }; + + var options = highCq ? CompilerOptions.HighCq : CompilerOptions.None; + + if (context.HasPtc && !singleStep) + { + options |= CompilerOptions.Relocatable; + } + + CompiledFunction compiledFunc = Compiler.Compile(cfg, argTypes, retType, options, RuntimeInformation.ProcessArchitecture); + + if (context.HasPtc && !singleStep) + { + Hash128 hash = Ptc.ComputeHash(Memory, address, funcSize); + + _ptc.WriteCompiledFunction(address, funcSize, hash, highCq, compiledFunc); + } + + GuestFunction func = compiledFunc.MapWithPointer(out IntPtr funcPointer); + + Allocators.ResetAll(); + + return new TranslatedFunction(func, funcPointer, counter, funcSize, highCq); + } + + private void BackgroundTranslate() + { + while (_threadCount != 0 && Queue.TryDequeue(out RejitRequest request)) + { + TranslatedFunction func = Translate(request.Address, request.Mode, highCq: true); + + Functions.AddOrUpdate(request.Address, func.GuestSize, func, (key, oldFunc) => + { + EnqueueForDeletion(key, oldFunc); + return func; + }); + + if (_ptc.Profiler.Enabled) + { + _ptc.Profiler.UpdateEntry(request.Address, request.Mode, highCq: true); + } + + RegisterFunction(request.Address, func); + } + } + + private readonly struct Range + { + public ulong Start { get; } + public ulong End { get; } + + public Range(ulong start, ulong end) + { + Start = start; + End = end; + } + } + + private static ControlFlowGraph EmitAndGetCFG( + ArmEmitterContext context, + Block[] blocks, + out Range range, + out Counter counter) + { + counter = null; + + ulong rangeStart = ulong.MaxValue; + ulong rangeEnd = 0; + + for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++) + { + Block block = blocks[blkIndex]; + + if (!block.Exit) + { + if (rangeStart > block.Address) + { + rangeStart = block.Address; + } + + if (rangeEnd < block.EndAddress) + { + rangeEnd = block.EndAddress; + } + } + + if (block.Address == context.EntryAddress) + { + if (!context.HighCq) + { + EmitRejitCheck(context, out counter); + } + + context.ClearQcFlag(); + } + + context.CurrBlock = block; + + context.MarkLabel(context.GetLabel(block.Address)); + + if (block.Exit) + { + // Left option here as it may be useful if we need to return to managed rather than tail call in + // future. (eg. for debug) + bool useReturns = false; + + InstEmitFlowHelper.EmitVirtualJump(context, Const(block.Address), isReturn: useReturns); + } + else + { + for (int opcIndex = 0; opcIndex < block.OpCodes.Count; opcIndex++) + { + OpCode opCode = block.OpCodes[opcIndex]; + + context.CurrOp = opCode; + + bool isLastOp = opcIndex == block.OpCodes.Count - 1; + + if (isLastOp) + { + context.SyncQcFlag(); + + if (block.Branch != null && !block.Branch.Exit && block.Branch.Address <= block.Address) + { + EmitSynchronization(context); + } + } + + Operand lblPredicateSkip = default; + + if (context.IsInIfThenBlock && context.CurrentIfThenBlockCond != Condition.Al) + { + lblPredicateSkip = Label(); + + InstEmitFlowHelper.EmitCondBranch(context, lblPredicateSkip, context.CurrentIfThenBlockCond.Invert()); + } + + if (opCode is OpCode32 op && op.Cond < Condition.Al) + { + lblPredicateSkip = Label(); + + InstEmitFlowHelper.EmitCondBranch(context, lblPredicateSkip, op.Cond.Invert()); + } + + if (opCode.Instruction.Emitter != null) + { + opCode.Instruction.Emitter(context); + } + else + { + throw new InvalidOperationException($"Invalid instruction \"{opCode.Instruction.Name}\"."); + } + + if (lblPredicateSkip != default) + { + context.MarkLabel(lblPredicateSkip); + } + + if (context.IsInIfThenBlock && opCode.Instruction.Name != InstName.It) + { + context.AdvanceIfThenBlockState(); + } + } + } + } + + range = new Range(rangeStart, rangeEnd); + + return context.GetControlFlowGraph(); + } + + internal static void EmitRejitCheck(ArmEmitterContext context, out Counter counter) + { + const int MinsCallForRejit = 100; + + counter = new Counter(context.CountTable); + + Operand lblEnd = Label(); + + Operand address = !context.HasPtc ? + Const(ref counter.Value) : + Const(ref counter.Value, Ptc.CountTableSymbol); + + Operand curCount = context.Load(OperandType.I32, address); + Operand count = context.Add(curCount, Const(1)); + context.Store(address, count); + context.BranchIf(lblEnd, curCount, Const(MinsCallForRejit), Comparison.NotEqual, BasicBlockFrequency.Cold); + + context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.EnqueueForRejit)), Const(context.EntryAddress)); + + context.MarkLabel(lblEnd); + } + + internal static void EmitSynchronization(EmitterContext context) + { + long countOffs = NativeContext.GetCounterOffset(); + + Operand lblNonZero = Label(); + Operand lblExit = Label(); + + Operand countAddr = context.Add(context.LoadArgument(OperandType.I64, 0), Const(countOffs)); + Operand count = context.Load(OperandType.I32, countAddr); + context.BranchIfTrue(lblNonZero, count, BasicBlockFrequency.Cold); + + Operand running = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.CheckSynchronization))); + context.BranchIfTrue(lblExit, running, BasicBlockFrequency.Cold); + + context.Return(Const(0L)); + + context.MarkLabel(lblNonZero); + count = context.Subtract(count, Const(1)); + context.Store(countAddr, count); + + context.MarkLabel(lblExit); + } + + public void InvalidateJitCacheRegion(ulong address, ulong size) + { + ulong[] overlapAddresses = Array.Empty(); + + int overlapsCount = Functions.GetOverlaps(address, size, ref overlapAddresses); + + if (overlapsCount != 0) + { + // If rejit is running, stop it as it may be trying to rejit a function on the invalidated region. + ClearRejitQueue(allowRequeue: true); + } + + for (int index = 0; index < overlapsCount; index++) + { + ulong overlapAddress = overlapAddresses[index]; + + if (Functions.TryGetValue(overlapAddress, out TranslatedFunction overlap)) + { + Functions.Remove(overlapAddress); + Volatile.Write(ref FunctionTable.GetValue(overlapAddress), FunctionTable.Fill); + EnqueueForDeletion(overlapAddress, overlap); + } + } + + // TODO: Remove overlapping functions from the JitCache aswell. + // This should be done safely, with a mechanism to ensure the function is not being executed. + } + + internal void EnqueueForRejit(ulong guestAddress, ExecutionMode mode) + { + Queue.Enqueue(guestAddress, mode); + } + + private void EnqueueForDeletion(ulong guestAddress, TranslatedFunction func) + { + _oldFuncs.Enqueue(new(guestAddress, func)); + } + + private void ClearJitCache() + { + // Ensure no attempt will be made to compile new functions due to rejit. + ClearRejitQueue(allowRequeue: false); + + List functions = Functions.AsList(); + + foreach (var func in functions) + { + JitCache.Unmap(func.FuncPointer); + + func.CallCounter?.Dispose(); + } + + Functions.Clear(); + + while (_oldFuncs.TryDequeue(out var kv)) + { + JitCache.Unmap(kv.Value.FuncPointer); + + kv.Value.CallCounter?.Dispose(); + } + } + + private void ClearRejitQueue(bool allowRequeue) + { + if (!allowRequeue) + { + Queue.Clear(); + + return; + } + + lock (Queue.Sync) + { + while (Queue.Count > 0 && Queue.TryDequeue(out RejitRequest request)) + { + if (Functions.TryGetValue(request.Address, out var func) && func.CallCounter != null) + { + Volatile.Write(ref func.CallCounter.Value, 0); + } + } + } + } + } +} diff --git a/src/ARMeilleure/Translation/TranslatorCache.cs b/src/ARMeilleure/Translation/TranslatorCache.cs new file mode 100644 index 00000000..11286381 --- /dev/null +++ b/src/ARMeilleure/Translation/TranslatorCache.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace ARMeilleure.Translation +{ + internal class TranslatorCache + { + private readonly IntervalTree _tree; + private readonly ReaderWriterLock _treeLock; + + public int Count => _tree.Count; + + public TranslatorCache() + { + _tree = new IntervalTree(); + _treeLock = new ReaderWriterLock(); + } + + public bool TryAdd(ulong address, ulong size, T value) + { + return AddOrUpdate(address, size, value, null); + } + + public bool AddOrUpdate(ulong address, ulong size, T value, Func updateFactoryCallback) + { + _treeLock.AcquireWriterLock(Timeout.Infinite); + bool result = _tree.AddOrUpdate(address, address + size, value, updateFactoryCallback); + _treeLock.ReleaseWriterLock(); + + return result; + } + + public T GetOrAdd(ulong address, ulong size, T value) + { + _treeLock.AcquireWriterLock(Timeout.Infinite); + value = _tree.GetOrAdd(address, address + size, value); + _treeLock.ReleaseWriterLock(); + + return value; + } + + public bool Remove(ulong address) + { + _treeLock.AcquireWriterLock(Timeout.Infinite); + bool removed = _tree.Remove(address) != 0; + _treeLock.ReleaseWriterLock(); + + return removed; + } + + public void Clear() + { + _treeLock.AcquireWriterLock(Timeout.Infinite); + _tree.Clear(); + _treeLock.ReleaseWriterLock(); + } + + public bool ContainsKey(ulong address) + { + _treeLock.AcquireReaderLock(Timeout.Infinite); + bool result = _tree.ContainsKey(address); + _treeLock.ReleaseReaderLock(); + + return result; + } + + public bool TryGetValue(ulong address, out T value) + { + _treeLock.AcquireReaderLock(Timeout.Infinite); + bool result = _tree.TryGet(address, out value); + _treeLock.ReleaseReaderLock(); + + return result; + } + + public int GetOverlaps(ulong address, ulong size, ref ulong[] overlaps) + { + _treeLock.AcquireReaderLock(Timeout.Infinite); + int count = _tree.Get(address, address + size, ref overlaps); + _treeLock.ReleaseReaderLock(); + + return count; + } + + public List AsList() + { + _treeLock.AcquireReaderLock(Timeout.Infinite); + List list = _tree.AsList(); + _treeLock.ReleaseReaderLock(); + + return list; + } + } +} diff --git a/src/ARMeilleure/Translation/TranslatorQueue.cs b/src/ARMeilleure/Translation/TranslatorQueue.cs new file mode 100644 index 00000000..fc0aa64f --- /dev/null +++ b/src/ARMeilleure/Translation/TranslatorQueue.cs @@ -0,0 +1,121 @@ +using ARMeilleure.Diagnostics; +using ARMeilleure.State; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace ARMeilleure.Translation +{ + /// + /// Represents a queue of . + /// + /// + /// This does not necessarily behave like a queue, i.e: a FIFO collection. + /// + sealed class TranslatorQueue : IDisposable + { + private bool _disposed; + private readonly Stack _requests; + private readonly HashSet _requestAddresses; + + /// + /// Gets the object used to synchronize access to the . + /// + public object Sync { get; } + + /// + /// Gets the number of requests in the . + /// + public int Count => _requests.Count; + + /// + /// Initializes a new instance of the class. + /// + public TranslatorQueue() + { + Sync = new object(); + + _requests = new Stack(); + _requestAddresses = new HashSet(); + } + + /// + /// Enqueues a request with the specified and . + /// + /// Address of request + /// of request + public void Enqueue(ulong address, ExecutionMode mode) + { + lock (Sync) + { + if (_requestAddresses.Add(address)) + { + _requests.Push(new RejitRequest(address, mode)); + + TranslatorEventSource.Log.RejitQueueAdd(1); + + Monitor.Pulse(Sync); + } + } + } + + /// + /// Tries to dequeue a . This will block the thread until a + /// is enqueued or the is disposed. + /// + /// dequeued + /// on success; otherwise + public bool TryDequeue(out RejitRequest result) + { + while (!_disposed) + { + lock (Sync) + { + if (_requests.TryPop(out result)) + { + _requestAddresses.Remove(result.Address); + + TranslatorEventSource.Log.RejitQueueAdd(-1); + + return true; + } + + Monitor.Wait(Sync); + } + } + + result = default; + + return false; + } + + /// + /// Clears the . + /// + public void Clear() + { + lock (Sync) + { + TranslatorEventSource.Log.RejitQueueAdd(-_requests.Count); + + _requests.Clear(); + _requestAddresses.Clear(); + + Monitor.PulseAll(Sync); + } + } + + /// + /// Releases all resources used by the instance. + /// + public void Dispose() + { + if (!_disposed) + { + _disposed = true; + + Clear(); + } + } + } +} diff --git a/src/ARMeilleure/Translation/TranslatorStubs.cs b/src/ARMeilleure/Translation/TranslatorStubs.cs new file mode 100644 index 00000000..69648df4 --- /dev/null +++ b/src/ARMeilleure/Translation/TranslatorStubs.cs @@ -0,0 +1,312 @@ +using ARMeilleure.Instructions; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation.Cache; +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Translation +{ + /// + /// Represents a stub manager. + /// + class TranslatorStubs : IDisposable + { + private static readonly Lazy _slowDispatchStub = new(GenerateSlowDispatchStub, isThreadSafe: true); + + private bool _disposed; + + private readonly Translator _translator; + private readonly Lazy _dispatchStub; + private readonly Lazy _dispatchLoop; + private readonly Lazy _contextWrapper; + + /// + /// Gets the dispatch stub. + /// + /// instance was disposed + public IntPtr DispatchStub + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + return _dispatchStub.Value; + } + } + + /// + /// Gets the slow dispatch stub. + /// + /// instance was disposed + public IntPtr SlowDispatchStub + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + return _slowDispatchStub.Value; + } + } + + /// + /// Gets the dispatch loop function. + /// + /// instance was disposed + public DispatcherFunction DispatchLoop + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + return _dispatchLoop.Value; + } + } + + /// + /// Gets the context wrapper function. + /// + /// instance was disposed + public WrapperFunction ContextWrapper + { + get + { + ObjectDisposedException.ThrowIf(_disposed, this); + + return _contextWrapper.Value; + } + } + + /// + /// Initializes a new instance of the class with the specified + /// instance. + /// + /// instance to use + /// is null + public TranslatorStubs(Translator translator) + { + ArgumentNullException.ThrowIfNull(translator); + + _translator = translator; + _dispatchStub = new(GenerateDispatchStub, isThreadSafe: true); + _dispatchLoop = new(GenerateDispatchLoop, isThreadSafe: true); + _contextWrapper = new(GenerateContextWrapper, isThreadSafe: true); + } + + /// + /// Releases all resources used by the instance. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases all unmanaged and optionally managed resources used by the instance. + /// + /// to dispose managed resources also; otherwise just unmanaged resouces + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (_dispatchStub.IsValueCreated) + { + JitCache.Unmap(_dispatchStub.Value); + } + + if (_dispatchLoop.IsValueCreated) + { + JitCache.Unmap(Marshal.GetFunctionPointerForDelegate(_dispatchLoop.Value)); + } + + _disposed = true; + } + } + + /// + /// Frees resources used by the instance. + /// + ~TranslatorStubs() + { + Dispose(false); + } + + /// + /// Generates a . + /// + /// Generated + private IntPtr GenerateDispatchStub() + { + var context = new EmitterContext(); + + Operand lblFallback = Label(); + Operand lblEnd = Label(); + + // Load the target guest address from the native context. + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + Operand guestAddress = context.Load(OperandType.I64, + context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()))); + + // Check if guest address is within range of the AddressTable. + Operand masked = context.BitwiseAnd(guestAddress, Const(~_translator.FunctionTable.Mask)); + context.BranchIfTrue(lblFallback, masked); + + Operand index = default; + Operand page = Const((long)_translator.FunctionTable.Base); + + for (int i = 0; i < _translator.FunctionTable.Levels.Length; i++) + { + ref var level = ref _translator.FunctionTable.Levels[i]; + + // level.Mask is not used directly because it is more often bigger than 32-bits, so it will not + // be encoded as an immediate on x86's bitwise and operation. + Operand mask = Const(level.Mask >> level.Index); + + index = context.BitwiseAnd(context.ShiftRightUI(guestAddress, Const(level.Index)), mask); + + if (i < _translator.FunctionTable.Levels.Length - 1) + { + page = context.Load(OperandType.I64, context.Add(page, context.ShiftLeft(index, Const(3)))); + context.BranchIfFalse(lblFallback, page); + } + } + + Operand hostAddress; + Operand hostAddressAddr = context.Add(page, context.ShiftLeft(index, Const(3))); + hostAddress = context.Load(OperandType.I64, hostAddressAddr); + context.Tailcall(hostAddress, nativeContext); + + context.MarkLabel(lblFallback); + hostAddress = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)), guestAddress); + context.Tailcall(hostAddress, nativeContext); + + var cfg = context.GetControlFlowGraph(); + var retType = OperandType.I64; + var argTypes = new[] { OperandType.I64 }; + + var func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map(); + + return Marshal.GetFunctionPointerForDelegate(func); + } + + /// + /// Generates a . + /// + /// Generated + private static IntPtr GenerateSlowDispatchStub() + { + var context = new EmitterContext(); + + // Load the target guest address from the native context. + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + Operand guestAddress = context.Load(OperandType.I64, + context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset()))); + + MethodInfo getFuncAddress = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)); + Operand hostAddress = context.Call(getFuncAddress, guestAddress); + context.Tailcall(hostAddress, nativeContext); + + var cfg = context.GetControlFlowGraph(); + var retType = OperandType.I64; + var argTypes = new[] { OperandType.I64 }; + + var func = Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map(); + + return Marshal.GetFunctionPointerForDelegate(func); + } + + /// + /// Emits code that syncs FP state before executing guest code, or returns it to normal. + /// + /// Emitter context for the method + /// Pointer to the native context + /// True if entering guest code, false otherwise + private void EmitSyncFpContext(EmitterContext context, Operand nativeContext, bool enter) + { + if (enter) + { + InstEmitSimdHelper.EnterArmFpMode(context, (flag) => + { + Operand flagAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRegisterOffset(new Register((int)flag, RegisterType.FpFlag)))); + return context.Load(OperandType.I32, flagAddress); + }); + } + else + { + InstEmitSimdHelper.ExitArmFpMode(context, (flag, value) => + { + Operand flagAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRegisterOffset(new Register((int)flag, RegisterType.FpFlag)))); + context.Store(flagAddress, value); + }); + } + } + + /// + /// Generates a function. + /// + /// function + private DispatcherFunction GenerateDispatchLoop() + { + var context = new EmitterContext(); + + Operand beginLbl = Label(); + Operand endLbl = Label(); + + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + Operand guestAddress = context.Copy( + context.AllocateLocal(OperandType.I64), + context.LoadArgument(OperandType.I64, 1)); + + Operand runningAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetRunningOffset())); + Operand dispatchAddress = context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset())); + + EmitSyncFpContext(context, nativeContext, true); + + context.MarkLabel(beginLbl); + context.Store(dispatchAddress, guestAddress); + context.Copy(guestAddress, context.Call(Const((ulong)DispatchStub), OperandType.I64, nativeContext)); + context.BranchIfFalse(endLbl, guestAddress); + context.BranchIfFalse(endLbl, context.Load(OperandType.I32, runningAddress)); + context.Branch(beginLbl); + + context.MarkLabel(endLbl); + + EmitSyncFpContext(context, nativeContext, false); + + context.Return(); + + var cfg = context.GetControlFlowGraph(); + var retType = OperandType.None; + var argTypes = new[] { OperandType.I64, OperandType.I64 }; + + return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map(); + } + + /// + /// Generates a function. + /// + /// function + private WrapperFunction GenerateContextWrapper() + { + var context = new EmitterContext(); + + Operand nativeContext = context.LoadArgument(OperandType.I64, 0); + Operand guestMethod = context.LoadArgument(OperandType.I64, 1); + + EmitSyncFpContext(context, nativeContext, true); + Operand returnValue = context.Call(guestMethod, OperandType.I64, nativeContext); + EmitSyncFpContext(context, nativeContext, false); + + context.Return(returnValue); + + var cfg = context.GetControlFlowGraph(); + var retType = OperandType.I64; + var argTypes = new[] { OperandType.I64, OperandType.I64 }; + + return Compiler.Compile(cfg, argTypes, retType, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map(); + } + } +} diff --git a/src/ARMeilleure/Translation/TranslatorTestMethods.cs b/src/ARMeilleure/Translation/TranslatorTestMethods.cs new file mode 100644 index 00000000..ab96019a --- /dev/null +++ b/src/ARMeilleure/Translation/TranslatorTestMethods.cs @@ -0,0 +1,148 @@ +using ARMeilleure.CodeGen.X86; +using ARMeilleure.IntermediateRepresentation; +using ARMeilleure.State; +using ARMeilleure.Translation; +using System; +using System.Runtime.InteropServices; +using static ARMeilleure.IntermediateRepresentation.Operand.Factory; + +namespace ARMeilleure.Translation +{ + public static class TranslatorTestMethods + { + public delegate int FpFlagsPInvokeTest(IntPtr managedMethod); + + private static bool SetPlatformFtz(EmitterContext context, bool ftz) + { + if (Optimizations.UseSse2) + { + Operand mxcsr = context.AddIntrinsicInt(Intrinsic.X86Stmxcsr); + + if (ftz) + { + mxcsr = context.BitwiseOr(mxcsr, Const((int)(Mxcsr.Ftz | Mxcsr.Um | Mxcsr.Dm))); + } + else + { + mxcsr = context.BitwiseAnd(mxcsr, Const(~(int)Mxcsr.Ftz)); + } + + context.AddIntrinsicNoRet(Intrinsic.X86Ldmxcsr, mxcsr); + + return true; + } + else if (Optimizations.UseAdvSimd) + { + Operand fpcr = context.AddIntrinsicInt(Intrinsic.Arm64MrsFpcr); + + if (ftz) + { + fpcr = context.BitwiseOr(fpcr, Const((int)FPCR.Fz)); + } + else + { + fpcr = context.BitwiseAnd(fpcr, Const(~(int)FPCR.Fz)); + } + + context.AddIntrinsicNoRet(Intrinsic.Arm64MsrFpcr, fpcr); + + return true; + } + else + { + return false; + } + } + + private static Operand FpBitsToInt(EmitterContext context, Operand fp) + { + Operand vec = context.VectorInsert(context.VectorZero(), fp, 0); + return context.VectorExtract(OperandType.I32, vec, 0); + } + + public static FpFlagsPInvokeTest GenerateFpFlagsPInvokeTest() + { + EmitterContext context = new EmitterContext(); + + Operand methodAddress = context.Copy(context.LoadArgument(OperandType.I64, 0)); + + // Verify that default dotnet fp state does not flush to zero. + // This is required for SoftFloat to function. + + // Denormal + zero != 0 + + Operand denormal = ConstF(BitConverter.Int32BitsToSingle(1)); // 1.40129846432e-45 + Operand zeroF = ConstF(0f); + Operand zero = Const(0); + + Operand result = context.Add(zeroF, denormal); + + // Must not be zero. + + Operand correct1Label = Label(); + + context.BranchIfFalse(correct1Label, context.ICompareEqual(FpBitsToInt(context, result), zero)); + + context.Return(Const(1)); + + context.MarkLabel(correct1Label); + + // Set flush to zero flag. If unsupported by the backend, just return true. + + if (!SetPlatformFtz(context, true)) + { + context.Return(Const(0)); + } + + // Denormal + zero == 0 + + Operand resultFz = context.Add(zeroF, denormal); + + // Must equal zero. + + Operand correct2Label = Label(); + + context.BranchIfTrue(correct2Label, context.ICompareEqual(FpBitsToInt(context, resultFz), zero)); + + SetPlatformFtz(context, false); + + context.Return(Const(2)); + + context.MarkLabel(correct2Label); + + // Call a managed method. This method should not change Fz state. + + context.Call(methodAddress, OperandType.None); + + // Denormal + zero == 0 + + Operand resultFz2 = context.Add(zeroF, denormal); + + // Must equal zero. + + Operand correct3Label = Label(); + + context.BranchIfTrue(correct3Label, context.ICompareEqual(FpBitsToInt(context, resultFz2), zero)); + + SetPlatformFtz(context, false); + + context.Return(Const(3)); + + context.MarkLabel(correct3Label); + + // Success. + + SetPlatformFtz(context, false); + + context.Return(Const(0)); + + // Compile and return the function. + + ControlFlowGraph cfg = context.GetControlFlowGraph(); + + OperandType[] argTypes = new OperandType[] { OperandType.I64 }; + + return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map(); + } + } +} diff --git a/src/Ryujinx.Audio.Backends.OpenAL/OpenALAudioBuffer.cs b/src/Ryujinx.Audio.Backends.OpenAL/OpenALAudioBuffer.cs new file mode 100644 index 00000000..050b52a9 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.OpenAL/OpenALAudioBuffer.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Audio.Backends.OpenAL +{ + class OpenALAudioBuffer + { + public int BufferId; + public ulong DriverIdentifier; + public ulong SampleCount; + } +} diff --git a/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs b/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs new file mode 100644 index 00000000..0c793f24 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs @@ -0,0 +1,167 @@ +using OpenTK.Audio.OpenAL; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading; +using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; + +namespace Ryujinx.Audio.Backends.OpenAL +{ + public class OpenALHardwareDeviceDriver : IHardwareDeviceDriver + { + private readonly ALDevice _device; + private readonly ALContext _context; + private readonly ManualResetEvent _updateRequiredEvent; + private readonly ManualResetEvent _pauseEvent; + private readonly ConcurrentDictionary _sessions; + private bool _stillRunning; + private Thread _updaterThread; + + public OpenALHardwareDeviceDriver() + { + _device = ALC.OpenDevice(""); + _context = ALC.CreateContext(_device, new ALContextAttributes()); + _updateRequiredEvent = new ManualResetEvent(false); + _pauseEvent = new ManualResetEvent(true); + _sessions = new ConcurrentDictionary(); + + _stillRunning = true; + _updaterThread = new Thread(Update) + { + Name = "HardwareDeviceDriver.OpenAL" + }; + + _updaterThread.Start(); + } + + public static bool IsSupported + { + get + { + try + { + return ALC.GetStringList(GetEnumerationStringList.DeviceSpecifier).Any(); + } + catch + { + return false; + } + } + } + + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume) + { + if (channelCount == 0) + { + channelCount = 2; + } + + if (sampleRate == 0) + { + sampleRate = Constants.TargetSampleRate; + } + + if (direction != Direction.Output) + { + throw new ArgumentException($"{direction}"); + } + else if (!SupportsChannelCount(channelCount)) + { + throw new ArgumentException($"{channelCount}"); + } + + OpenALHardwareDeviceSession session = new OpenALHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount, volume); + + _sessions.TryAdd(session, 0); + + return session; + } + + internal bool Unregister(OpenALHardwareDeviceSession session) + { + return _sessions.TryRemove(session, out _); + } + + public ManualResetEvent GetUpdateRequiredEvent() + { + return _updateRequiredEvent; + } + + public ManualResetEvent GetPauseEvent() + { + return _pauseEvent; + } + + private void Update() + { + ALC.MakeContextCurrent(_context); + + while (_stillRunning) + { + bool updateRequired = false; + + foreach (OpenALHardwareDeviceSession session in _sessions.Keys) + { + if (session.Update()) + { + updateRequired = true; + } + } + + if (updateRequired) + { + _updateRequiredEvent.Set(); + } + + // If it's not slept it will waste cycles. + Thread.Sleep(10); + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _stillRunning = false; + + foreach (OpenALHardwareDeviceSession session in _sessions.Keys) + { + session.Dispose(); + } + + ALC.DestroyContext(_context); + ALC.CloseDevice(_device); + + _pauseEvent.Dispose(); + } + } + + public bool SupportsSampleRate(uint sampleRate) + { + return true; + } + + public bool SupportsSampleFormat(SampleFormat sampleFormat) + { + return true; + } + + public bool SupportsChannelCount(uint channelCount) + { + return channelCount == 1 || channelCount == 2 || channelCount == 6; + } + + public bool SupportsDirection(Direction direction) + { + return direction == Direction.Output; + } + } +} diff --git a/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs new file mode 100644 index 00000000..ac3319e0 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs @@ -0,0 +1,212 @@ +using OpenTK.Audio.OpenAL; +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Common; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.Audio.Backends.OpenAL +{ + class OpenALHardwareDeviceSession : HardwareDeviceSessionOutputBase + { + private OpenALHardwareDeviceDriver _driver; + private int _sourceId; + private ALFormat _targetFormat; + private bool _isActive; + private Queue _queuedBuffers; + private ulong _playedSampleCount; + + private object _lock = new object(); + + public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + { + _driver = driver; + _queuedBuffers = new Queue(); + _sourceId = AL.GenSource(); + _targetFormat = GetALFormat(); + _isActive = false; + _playedSampleCount = 0; + SetVolume(requestedVolume); + } + + private ALFormat GetALFormat() + { + switch (RequestedSampleFormat) + { + case SampleFormat.PcmInt16: + switch (RequestedChannelCount) + { + case 1: + return ALFormat.Mono16; + case 2: + return ALFormat.Stereo16; + case 6: + return ALFormat.Multi51Chn16Ext; + default: + throw new NotImplementedException($"Unsupported channel config {RequestedChannelCount}"); + } + default: + throw new NotImplementedException($"Unsupported sample format {RequestedSampleFormat}"); + } + } + + public override void PrepareToClose() { } + + private void StartIfNotPlaying() + { + AL.GetSource(_sourceId, ALGetSourcei.SourceState, out int stateInt); + + ALSourceState State = (ALSourceState)stateInt; + + if (State != ALSourceState.Playing) + { + AL.SourcePlay(_sourceId); + } + } + + public override void QueueBuffer(AudioBuffer buffer) + { + lock (_lock) + { + OpenALAudioBuffer driverBuffer = new OpenALAudioBuffer + { + DriverIdentifier = buffer.DataPointer, + BufferId = AL.GenBuffer(), + SampleCount = GetSampleCount(buffer) + }; + + AL.BufferData(driverBuffer.BufferId, _targetFormat, buffer.Data, (int)RequestedSampleRate); + + _queuedBuffers.Enqueue(driverBuffer); + + AL.SourceQueueBuffer(_sourceId, driverBuffer.BufferId); + + if (_isActive) + { + StartIfNotPlaying(); + } + } + } + + public override void SetVolume(float volume) + { + lock (_lock) + { + AL.Source(_sourceId, ALSourcef.Gain, volume); + } + } + + public override float GetVolume() + { + AL.GetSource(_sourceId, ALSourcef.Gain, out float volume); + + return volume; + } + + public override void Start() + { + lock (_lock) + { + _isActive = true; + + StartIfNotPlaying(); + } + } + + public override void Stop() + { + lock (_lock) + { + SetVolume(0.0f); + + AL.SourceStop(_sourceId); + + _isActive = false; + } + } + + public override void UnregisterBuffer(AudioBuffer buffer) { } + + public override bool WasBufferFullyConsumed(AudioBuffer buffer) + { + lock (_lock) + { + if (!_queuedBuffers.TryPeek(out OpenALAudioBuffer driverBuffer)) + { + return true; + } + + return driverBuffer.DriverIdentifier != buffer.DataPointer; + } + } + + public override ulong GetPlayedSampleCount() + { + lock (_lock) + { + return _playedSampleCount; + } + } + + public bool Update() + { + lock (_lock) + { + if (_isActive) + { + AL.GetSource(_sourceId, ALGetSourcei.BuffersProcessed, out int releasedCount); + + if (releasedCount > 0) + { + int[] bufferIds = new int[releasedCount]; + + AL.SourceUnqueueBuffers(_sourceId, releasedCount, bufferIds); + + int i = 0; + + while (_queuedBuffers.TryPeek(out OpenALAudioBuffer buffer) && i < bufferIds.Length) + { + if (buffer.BufferId == bufferIds[i]) + { + _playedSampleCount += buffer.SampleCount; + + _queuedBuffers.TryDequeue(out _); + + i++; + } + } + + Debug.Assert(i == bufferIds.Length, "Unknown buffer ids found!"); + + AL.DeleteBuffers(bufferIds); + } + + return releasedCount > 0; + } + + return false; + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && _driver.Unregister(this)) + { + lock (_lock) + { + PrepareToClose(); + Stop(); + + AL.DeleteSource(_sourceId); + } + } + } + + public override void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj b/src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj new file mode 100644 index 00000000..115a3760 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj @@ -0,0 +1,15 @@ + + + + net7.0 + + + + + + + + + + + diff --git a/src/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj b/src/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj new file mode 100644 index 00000000..525f1f5b --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj @@ -0,0 +1,13 @@ + + + + net7.0 + true + + + + + + + + diff --git a/src/Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs b/src/Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs new file mode 100644 index 00000000..71ef414a --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Audio.Backends.SDL2 +{ + class SDL2AudioBuffer + { + public readonly ulong DriverIdentifier; + public readonly ulong SampleCount; + public ulong SamplePlayed; + + public SDL2AudioBuffer(ulong driverIdentifier, ulong sampleCount) + { + DriverIdentifier = driverIdentifier; + SampleCount = sampleCount; + SamplePlayed = 0; + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs new file mode 100644 index 00000000..b190b4c8 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs @@ -0,0 +1,176 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using Ryujinx.SDL2.Common; +using System; +using System.Collections.Concurrent; +using System.Threading; + +using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; +using static SDL2.SDL; + +namespace Ryujinx.Audio.Backends.SDL2 +{ + public class SDL2HardwareDeviceDriver : IHardwareDeviceDriver + { + private readonly ManualResetEvent _updateRequiredEvent; + private readonly ManualResetEvent _pauseEvent; + private readonly ConcurrentDictionary _sessions; + + public SDL2HardwareDeviceDriver() + { + _updateRequiredEvent = new ManualResetEvent(false); + _pauseEvent = new ManualResetEvent(true); + _sessions = new ConcurrentDictionary(); + + SDL2Driver.Instance.Initialize(); + } + + public static bool IsSupported => IsSupportedInternal(); + + private static bool IsSupportedInternal() + { + uint device = OpenStream(SampleFormat.PcmInt16, Constants.TargetSampleRate, Constants.ChannelCountMax, Constants.TargetSampleCount, null); + + if (device != 0) + { + SDL_CloseAudioDevice(device); + } + + return device != 0; + } + + public ManualResetEvent GetUpdateRequiredEvent() + { + return _updateRequiredEvent; + } + + public ManualResetEvent GetPauseEvent() + { + return _pauseEvent; + } + + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume) + { + if (channelCount == 0) + { + channelCount = 2; + } + + if (sampleRate == 0) + { + sampleRate = Constants.TargetSampleRate; + } + + if (direction != Direction.Output) + { + throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!"); + } + + SDL2HardwareDeviceSession session = new SDL2HardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount, volume); + + _sessions.TryAdd(session, 0); + + return session; + } + + internal bool Unregister(SDL2HardwareDeviceSession session) + { + return _sessions.TryRemove(session, out _); + } + + private static SDL_AudioSpec GetSDL2Spec(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount) + { + return new SDL_AudioSpec + { + channels = (byte)requestedChannelCount, + format = GetSDL2Format(requestedSampleFormat), + freq = (int)requestedSampleRate, + samples = (ushort)sampleCount + }; + } + + internal static ushort GetSDL2Format(SampleFormat format) + { + return format switch + { + SampleFormat.PcmInt8 => AUDIO_S8, + SampleFormat.PcmInt16 => AUDIO_S16, + SampleFormat.PcmInt32 => AUDIO_S32, + SampleFormat.PcmFloat => AUDIO_F32, + _ => throw new ArgumentException($"Unsupported sample format {format}"), + }; + } + + internal static uint OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount, SDL_AudioCallback callback) + { + SDL_AudioSpec desired = GetSDL2Spec(requestedSampleFormat, requestedSampleRate, requestedChannelCount, sampleCount); + + desired.callback = callback; + + uint device = SDL_OpenAudioDevice(IntPtr.Zero, 0, ref desired, out SDL_AudioSpec got, 0); + + if (device == 0) + { + Logger.Error?.Print(LogClass.Application, + $"SDL2 open audio device initialization failed with error \"{SDL_GetError()}\""); + + return 0; + } + + bool isValid = got.format == desired.format && got.freq == desired.freq && got.channels == desired.channels; + + if (!isValid) + { + Logger.Error?.Print(LogClass.Application, "SDL2 open audio device is not valid"); + SDL_CloseAudioDevice(device); + + return 0; + } + + return device; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + foreach (SDL2HardwareDeviceSession session in _sessions.Keys) + { + session.Dispose(); + } + + SDL2Driver.Instance.Dispose(); + + _pauseEvent.Dispose(); + } + } + + public bool SupportsSampleRate(uint sampleRate) + { + return true; + } + + public bool SupportsSampleFormat(SampleFormat sampleFormat) + { + return sampleFormat != SampleFormat.PcmInt24; + } + + public bool SupportsChannelCount(uint channelCount) + { + return true; + } + + public bool SupportsDirection(Direction direction) + { + // TODO: add direction input when supported. + return direction == Direction.Output; + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs new file mode 100644 index 00000000..14310b93 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs @@ -0,0 +1,230 @@ +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Collections.Concurrent; +using System.Threading; + +using static SDL2.SDL; + +namespace Ryujinx.Audio.Backends.SDL2 +{ + class SDL2HardwareDeviceSession : HardwareDeviceSessionOutputBase + { + private SDL2HardwareDeviceDriver _driver; + private ConcurrentQueue _queuedBuffers; + private DynamicRingBuffer _ringBuffer; + private ulong _playedSampleCount; + private ManualResetEvent _updateRequiredEvent; + private uint _outputStream; + private bool _hasSetupError; + private SDL_AudioCallback _callbackDelegate; + private int _bytesPerFrame; + private uint _sampleCount; + private bool _started; + private float _volume; + private ushort _nativeSampleFormat; + + public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + { + _driver = driver; + _updateRequiredEvent = _driver.GetUpdateRequiredEvent(); + _queuedBuffers = new ConcurrentQueue(); + _ringBuffer = new DynamicRingBuffer(); + _callbackDelegate = Update; + _bytesPerFrame = BackendHelper.GetSampleSize(RequestedSampleFormat) * (int)RequestedChannelCount; + _nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat); + _sampleCount = uint.MaxValue; + _started = false; + _volume = requestedVolume; + } + + private void EnsureAudioStreamSetup(AudioBuffer buffer) + { + uint bufferSampleCount = (uint)GetSampleCount(buffer); + bool needAudioSetup = (_outputStream == 0 && !_hasSetupError) || + (bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount); + + if (needAudioSetup) + { + _sampleCount = Math.Max(Constants.TargetSampleCount, bufferSampleCount); + + uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate); + + _hasSetupError = newOutputStream == 0; + + if (!_hasSetupError) + { + if (_outputStream != 0) + { + SDL_CloseAudioDevice(_outputStream); + } + + _outputStream = newOutputStream; + + SDL_PauseAudioDevice(_outputStream, _started ? 0 : 1); + + Logger.Info?.Print(LogClass.Audio, $"New audio stream setup with a target sample count of {_sampleCount}"); + } + } + } + + private unsafe void Update(IntPtr userdata, IntPtr stream, int streamLength) + { + Span streamSpan = new Span((void*)stream, streamLength); + + int maxFrameCount = (int)GetSampleCount(streamLength); + int bufferedFrames = _ringBuffer.Length / _bytesPerFrame; + + int frameCount = Math.Min(bufferedFrames, maxFrameCount); + + if (frameCount == 0) + { + // SDL2 left the responsibility to the user to clear the buffer. + streamSpan.Fill(0); + + return; + } + + byte[] samples = new byte[frameCount * _bytesPerFrame]; + + _ringBuffer.Read(samples, 0, samples.Length); + + fixed (byte* p = samples) + { + IntPtr pStreamSrc = (IntPtr)p; + + // Zero the dest buffer + streamSpan.Fill(0); + + // Apply volume to written data + SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_volume * SDL_MIX_MAXVOLUME)); + } + + ulong sampleCount = GetSampleCount(samples.Length); + + ulong availaibleSampleCount = sampleCount; + + bool needUpdate = false; + + while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SDL2AudioBuffer driverBuffer)) + { + ulong sampleStillNeeded = driverBuffer.SampleCount - Interlocked.Read(ref driverBuffer.SamplePlayed); + ulong playedAudioBufferSampleCount = Math.Min(sampleStillNeeded, availaibleSampleCount); + + ulong currentSamplePlayed = Interlocked.Add(ref driverBuffer.SamplePlayed, playedAudioBufferSampleCount); + availaibleSampleCount -= playedAudioBufferSampleCount; + + if (currentSamplePlayed == driverBuffer.SampleCount) + { + _queuedBuffers.TryDequeue(out _); + + needUpdate = true; + } + + Interlocked.Add(ref _playedSampleCount, playedAudioBufferSampleCount); + } + + // Notify the output if needed. + if (needUpdate) + { + _updateRequiredEvent.Set(); + } + } + + public override ulong GetPlayedSampleCount() + { + return Interlocked.Read(ref _playedSampleCount); + } + + public override float GetVolume() + { + return _volume; + } + + public override void PrepareToClose() { } + + public override void QueueBuffer(AudioBuffer buffer) + { + EnsureAudioStreamSetup(buffer); + + if (_outputStream != 0) + { + SDL2AudioBuffer driverBuffer = new SDL2AudioBuffer(buffer.DataPointer, GetSampleCount(buffer)); + + _ringBuffer.Write(buffer.Data, 0, buffer.Data.Length); + + _queuedBuffers.Enqueue(driverBuffer); + } + else + { + Interlocked.Add(ref _playedSampleCount, GetSampleCount(buffer)); + + _updateRequiredEvent.Set(); + } + } + + public override void SetVolume(float volume) + { + _volume = volume; + } + + public override void Start() + { + if (!_started) + { + if (_outputStream != 0) + { + SDL_PauseAudioDevice(_outputStream, 0); + } + + _started = true; + } + } + + public override void Stop() + { + if (_started) + { + if (_outputStream != 0) + { + SDL_PauseAudioDevice(_outputStream, 1); + } + + _started = false; + } + } + + public override void UnregisterBuffer(AudioBuffer buffer) { } + + public override bool WasBufferFullyConsumed(AudioBuffer buffer) + { + if (!_queuedBuffers.TryPeek(out SDL2AudioBuffer driverBuffer)) + { + return true; + } + + return driverBuffer.DriverIdentifier != buffer.DataPointer; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && _driver.Unregister(this)) + { + PrepareToClose(); + Stop(); + + if (_outputStream != 0) + { + SDL_CloseAudioDevice(_outputStream); + } + } + } + + public override void Dispose() + { + Dispose(true); + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIo.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIo.cs new file mode 100644 index 00000000..9c3e686d --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIo.cs @@ -0,0 +1,178 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public static partial class SoundIo + { + private const string LibraryName = "libsoundio"; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public unsafe delegate void OnDeviceChangeNativeDelegate(IntPtr ctx); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public unsafe delegate void OnBackendDisconnectedDelegate(IntPtr ctx, SoundIoError err); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public unsafe delegate void OnEventsSignalDelegate(IntPtr ctx); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public unsafe delegate void EmitRtPrioWarningDelegate(); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public unsafe delegate void JackCallbackDelegate(IntPtr msg); + + [StructLayout(LayoutKind.Sequential)] + public struct SoundIoStruct + { + public IntPtr UserData; + public IntPtr OnDeviceChange; + public IntPtr OnBackendDisconnected; + public IntPtr OnEventsSignal; + public SoundIoBackend CurrentBackend; + public IntPtr ApplicationName; + public IntPtr EmitRtPrioWarning; + public IntPtr JackInfoCallback; + public IntPtr JackErrorCallback; + } + + public struct SoundIoChannelLayout + { + public IntPtr Name; + public int ChannelCount; + public Array24 Channels; + + public static IntPtr GetDefault(int channelCount) + { + return soundio_channel_layout_get_default(channelCount); + } + + public static unsafe SoundIoChannelLayout GetDefaultValue(int channelCount) + { + return Unsafe.AsRef((SoundIoChannelLayout*)GetDefault(channelCount)); + } + } + + public struct SoundIoSampleRateRange + { + public int Min; + public int Max; + } + + public struct SoundIoDevice + { + public IntPtr SoundIo; + public IntPtr Id; + public IntPtr Name; + public SoundIoDeviceAim Aim; + public IntPtr Layouts; + public int LayoutCount; + public SoundIoChannelLayout CurrentLayout; + public IntPtr Formats; + public int FormatCount; + public SoundIoFormat CurrentFormat; + public IntPtr SampleRates; + public int SampleRateCount; + public int SampleRateCurrent; + public double SoftwareLatencyMin; + public double SoftwareLatencyMax; + public double SoftwareLatencyCurrent; + public bool IsRaw; + public int RefCount; + public SoundIoError ProbeError; + } + + public struct SoundIoOutStream + { + public IntPtr Device; + public SoundIoFormat Format; + public int SampleRate; + public SoundIoChannelLayout Layout; + public double SoftwareLatency; + public float Volume; + public IntPtr UserData; + public IntPtr WriteCallback; + public IntPtr UnderflowCallback; + public IntPtr ErrorCallback; + public IntPtr Name; + public bool NonTerminalHint; + public int BytesPerFrame; + public int BytesPerSample; + public SoundIoError LayoutError; + } + + public struct SoundIoChannelArea + { + public IntPtr Pointer; + public int Step; + } + + [LibraryImport(LibraryName)] + public static partial IntPtr soundio_create(); + + [LibraryImport(LibraryName)] + public static partial SoundIoError soundio_connect(IntPtr ctx); + + [LibraryImport(LibraryName)] + public static partial void soundio_disconnect(IntPtr ctx); + + [LibraryImport(LibraryName)] + public static partial void soundio_flush_events(IntPtr ctx); + + [LibraryImport(LibraryName)] + public static partial int soundio_output_device_count(IntPtr ctx); + + [LibraryImport(LibraryName)] + public static partial int soundio_default_output_device_index(IntPtr ctx); + + [LibraryImport(LibraryName)] + public static partial IntPtr soundio_get_output_device(IntPtr ctx, int index); + + [LibraryImport(LibraryName)] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool soundio_device_supports_format(IntPtr devCtx, SoundIoFormat format); + + [LibraryImport(LibraryName)] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool soundio_device_supports_layout(IntPtr devCtx, IntPtr layout); + + [LibraryImport(LibraryName)] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool soundio_device_supports_sample_rate(IntPtr devCtx, int sampleRate); + + [LibraryImport(LibraryName)] + public static partial IntPtr soundio_outstream_create(IntPtr devCtx); + + [LibraryImport(LibraryName)] + public static partial SoundIoError soundio_outstream_open(IntPtr outStreamCtx); + + [LibraryImport(LibraryName)] + public static partial SoundIoError soundio_outstream_start(IntPtr outStreamCtx); + + [LibraryImport(LibraryName)] + public static partial SoundIoError soundio_outstream_begin_write(IntPtr outStreamCtx, IntPtr areas, IntPtr frameCount); + + [LibraryImport(LibraryName)] + public static partial SoundIoError soundio_outstream_end_write(IntPtr outStreamCtx); + + [LibraryImport(LibraryName)] + public static partial SoundIoError soundio_outstream_pause(IntPtr devCtx, [MarshalAs(UnmanagedType.Bool)] bool pause); + + [LibraryImport(LibraryName)] + public static partial SoundIoError soundio_outstream_set_volume(IntPtr devCtx, double volume); + + [LibraryImport(LibraryName)] + public static partial void soundio_outstream_destroy(IntPtr streamCtx); + + [LibraryImport(LibraryName)] + public static partial void soundio_destroy(IntPtr ctx); + + [LibraryImport(LibraryName)] + public static partial IntPtr soundio_channel_layout_get_default(int channelCount); + + [LibraryImport(LibraryName)] + public static partial IntPtr soundio_strerror(SoundIoError err); + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoBackend.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoBackend.cs new file mode 100644 index 00000000..92f8ea37 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoBackend.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public enum SoundIoBackend : int + { + None = 0, + Jack = 1, + PulseAudio = 2, + Alsa = 3, + CoreAudio = 4, + Wasapi = 5, + Dummy = 6 + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoChannelId.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoChannelId.cs new file mode 100644 index 00000000..70346e0b --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoChannelId.cs @@ -0,0 +1,75 @@ +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public enum SoundIoChannelId + { + Invalid = 0, + FrontLeft = 1, + FrontRight = 2, + FrontCenter = 3, + Lfe = 4, + BackLeft = 5, + BackRight = 6, + FrontLeftCenter = 7, + FrontRightCenter = 8, + BackCenter = 9, + SideLeft = 10, + SideRight = 11, + TopCenter = 12, + TopFrontLeft = 13, + TopFrontCenter = 14, + TopFrontRight = 15, + TopBackLeft = 16, + TopBackCenter = 17, + TopBackRight = 18, + BackLeftCenter = 19, + BackRightCenter = 20, + FrontLeftWide = 21, + FrontRightWide = 22, + FrontLeftHigh = 23, + FrontCenterHigh = 24, + FrontRightHigh = 25, + TopFrontLeftCenter = 26, + TopFrontRightCenter = 27, + TopSideLeft = 28, + TopSideRight = 29, + LeftLfe = 30, + RightLfe = 31, + Lfe2 = 32, + BottomCenter = 33, + BottomLeftCenter = 34, + BottomRightCenter = 35, + MsMid = 36, + MsSide = 37, + AmbisonicW = 38, + AmbisonicX = 39, + AmbisonicY = 40, + AmbisonicZ = 41, + XyX = 42, + XyY = 43, + HeadphonesLeft = 44, + HeadphonesRight = 45, + ClickTrack = 46, + ForeignLanguage = 47, + HearingImpaired = 48, + Narration = 49, + Haptic = 50, + DialogCentricMix = 51, + Aux = 52, + Aux0 = 53, + Aux1 = 54, + Aux2 = 55, + Aux3 = 56, + Aux4 = 57, + Aux5 = 58, + Aux6 = 59, + Aux7 = 60, + Aux8 = 61, + Aux9 = 62, + Aux10 = 63, + Aux11 = 64, + Aux12 = 65, + Aux13 = 66, + Aux14 = 67, + Aux15 = 68, + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoContext.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoContext.cs new file mode 100644 index 00000000..3744c2e6 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoContext.cs @@ -0,0 +1,107 @@ +using System; +using System.Reflection.Metadata; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo; + +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public class SoundIoContext : IDisposable + { + private IntPtr _context; + private Action _onBackendDisconnect; + private OnBackendDisconnectedDelegate _onBackendDisconnectNative; + + public IntPtr Context => _context; + + internal SoundIoContext(IntPtr context) + { + _context = context; + _onBackendDisconnect = null; + _onBackendDisconnectNative = null; + } + + public SoundIoError Connect() => soundio_connect(_context); + public void Disconnect() => soundio_disconnect(_context); + + public void FlushEvents() => soundio_flush_events(_context); + + public int OutputDeviceCount => soundio_output_device_count(_context); + + public int DefaultOutputDeviceIndex => soundio_default_output_device_index(_context); + + public Action OnBackendDisconnect + { + get { return _onBackendDisconnect; } + set + { + _onBackendDisconnect = value; + + if (_onBackendDisconnect == null) + { + _onBackendDisconnectNative = null; + } + else + { + _onBackendDisconnectNative = (ctx, err) => _onBackendDisconnect(err); + } + + GetContext().OnBackendDisconnected = Marshal.GetFunctionPointerForDelegate(_onBackendDisconnectNative); + } + } + + private ref SoundIoStruct GetContext() + { + unsafe + { + return ref Unsafe.AsRef((SoundIoStruct*)_context); + } + } + + public SoundIoDeviceContext GetOutputDevice(int index) + { + IntPtr deviceContext = soundio_get_output_device(_context, index); + + if (deviceContext == IntPtr.Zero) + { + return null; + } + + return new SoundIoDeviceContext(deviceContext); + } + + public static SoundIoContext Create() + { + IntPtr context = soundio_create(); + + if (context == IntPtr.Zero) + { + return null; + } + + return new SoundIoContext(context); + } + + protected virtual void Dispose(bool disposing) + { + IntPtr currentContext = Interlocked.Exchange(ref _context, IntPtr.Zero); + + if (currentContext != IntPtr.Zero) + { + soundio_destroy(currentContext); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~SoundIoContext() + { + Dispose(false); + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceAim.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceAim.cs new file mode 100644 index 00000000..a0689d6d --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceAim.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public enum SoundIoDeviceAim + { + SoundIoDeviceAimInput = 0, + SoundIoDeviceAimOutput = 1 + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceContext.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceContext.cs new file mode 100644 index 00000000..42bcc6e3 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoDeviceContext.cs @@ -0,0 +1,49 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo; + +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public class SoundIoDeviceContext + { + private readonly IntPtr _context; + + public IntPtr Context => _context; + + internal SoundIoDeviceContext(IntPtr context) + { + _context = context; + } + + private ref SoundIoDevice GetDeviceContext() + { + unsafe + { + return ref Unsafe.AsRef((SoundIoDevice*)_context); + } + } + + public bool IsRaw => GetDeviceContext().IsRaw; + + public string Id => Marshal.PtrToStringAnsi(GetDeviceContext().Id); + + public bool SupportsSampleRate(int sampleRate) => soundio_device_supports_sample_rate(_context, sampleRate); + + public bool SupportsFormat(SoundIoFormat format) => soundio_device_supports_format(_context, format); + + public bool SupportsChannelCount(int channelCount) => soundio_device_supports_layout(_context, SoundIoChannelLayout.GetDefault(channelCount)); + + public SoundIoOutStreamContext CreateOutStream() + { + IntPtr context = soundio_outstream_create(_context); + + if (context == IntPtr.Zero) + { + return null; + } + + return new SoundIoOutStreamContext(context); + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoError.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoError.cs new file mode 100644 index 00000000..9e33fa19 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoError.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public enum SoundIoError + { + None = 0, + NoMem = 1, + InitAudioBackend = 2, + SystemResources = 3, + OpeningDevice = 4, + NoSuchDevice = 5, + Invalid = 6, + BackendUnavailable = 7, + Streaming = 8, + IncompatibleDevice = 9, + NoSuchClient = 10, + IncompatibleBackend = 11, + BackendDisconnected = 12, + Interrupted = 13, + Underflow = 14, + EncodingString = 15, + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoException.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoException.cs new file mode 100644 index 00000000..a033356e --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoException.cs @@ -0,0 +1,11 @@ +using System; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo; + +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + internal class SoundIoException : Exception + { + internal SoundIoException(SoundIoError error) : base(Marshal.PtrToStringAnsi(soundio_strerror(error))) { } + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoFormat.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoFormat.cs new file mode 100644 index 00000000..0eee9780 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoFormat.cs @@ -0,0 +1,25 @@ +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public enum SoundIoFormat + { + Invalid = 0, + S8 = 1, + U8 = 2, + S16LE = 3, + S16BE = 4, + U16LE = 5, + U16BE = 6, + S24LE = 7, + S24BE = 8, + U24LE = 9, + U24BE = 10, + S32LE = 11, + S32BE = 12, + U32LE = 13, + U32BE = 14, + Float32LE = 15, + Float32BE = 16, + Float64LE = 17, + Float64BE = 18, + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoOutStreamContext.cs b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoOutStreamContext.cs new file mode 100644 index 00000000..2e432b31 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoOutStreamContext.cs @@ -0,0 +1,164 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo; + +namespace Ryujinx.Audio.Backends.SoundIo.Native +{ + public class SoundIoOutStreamContext : IDisposable + { + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private unsafe delegate void WriteCallbackDelegate(IntPtr ctx, int frameCountMin, int frameCountMax); + + private IntPtr _context; + private IntPtr _nameStored; + private Action _writeCallback; + private WriteCallbackDelegate _writeCallbackNative; + + public IntPtr Context => _context; + + internal SoundIoOutStreamContext(IntPtr context) + { + _context = context; + _nameStored = IntPtr.Zero; + _writeCallback = null; + _writeCallbackNative = null; + } + + private ref SoundIoOutStream GetOutContext() + { + unsafe + { + return ref Unsafe.AsRef((SoundIoOutStream*)_context); + } + } + + public string Name + { + get => Marshal.PtrToStringAnsi(GetOutContext().Name); + set + { + var context = GetOutContext(); + + if (_nameStored != IntPtr.Zero && context.Name == _nameStored) + { + Marshal.FreeHGlobal(_nameStored); + } + + _nameStored = Marshal.StringToHGlobalAnsi(value); + GetOutContext().Name = _nameStored; + } + } + + public SoundIoChannelLayout Layout + { + get => GetOutContext().Layout; + set => GetOutContext().Layout = value; + } + + public SoundIoFormat Format + { + get => GetOutContext().Format; + set => GetOutContext().Format = value; + } + + public int SampleRate + { + get => GetOutContext().SampleRate; + set => GetOutContext().SampleRate = value; + } + + public float Volume + { + get => GetOutContext().Volume; + set => GetOutContext().Volume = value; + } + + public int BytesPerFrame + { + get => GetOutContext().BytesPerFrame; + set => GetOutContext().BytesPerFrame = value; + } + + public int BytesPerSample + { + get => GetOutContext().BytesPerSample; + set => GetOutContext().BytesPerSample = value; + } + + public Action WriteCallback + { + get { return _writeCallback; } + set + { + _writeCallback = value; + + if (_writeCallback == null) + { + _writeCallbackNative = null; + } + else + { + _writeCallbackNative = (ctx, frameCountMin, frameCountMax) => _writeCallback(frameCountMin, frameCountMax); + } + + GetOutContext().WriteCallback = Marshal.GetFunctionPointerForDelegate(_writeCallbackNative); + } + } + + private static void CheckError(SoundIoError error) + { + if (error != SoundIoError.None) + { + throw new SoundIoException(error); + } + } + + public void Open() => CheckError(soundio_outstream_open(_context)); + + public void Start() => CheckError(soundio_outstream_start(_context)); + + public void Pause(bool pause) => CheckError(soundio_outstream_pause(_context, pause)); + + public void SetVolume(double volume) => CheckError(soundio_outstream_set_volume(_context, volume)); + + public Span BeginWrite(ref int frameCount) + { + IntPtr arenas = default; + int nativeFrameCount = frameCount; + + unsafe + { + var frameCountPtr = &nativeFrameCount; + var arenasPtr = &arenas; + CheckError(soundio_outstream_begin_write(_context, (IntPtr)arenasPtr, (IntPtr)frameCountPtr)); + + frameCount = *frameCountPtr; + + return new Span((void*)arenas, Layout.ChannelCount); + } + } + + public void EndWrite() => CheckError(soundio_outstream_end_write(_context)); + + protected virtual void Dispose(bool disposing) + { + if (_context != IntPtr.Zero) + { + soundio_outstream_destroy(_context); + _context = IntPtr.Zero; + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~SoundIoOutStreamContext() + { + Dispose(false); + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dll b/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dll new file mode 100644 index 00000000..48804312 Binary files /dev/null and b/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dll differ diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dylib b/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dylib new file mode 100644 index 00000000..10171f4f Binary files /dev/null and b/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dylib differ diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.so b/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.so new file mode 100644 index 00000000..87c8b506 Binary files /dev/null and b/src/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.so differ diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj b/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj new file mode 100644 index 00000000..9f242dbe --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj @@ -0,0 +1,28 @@ + + + + net7.0 + true + win10-x64;linux-x64;osx-x64 + + + + + + + + + PreserveNewest + libsoundio.dll + + + PreserveNewest + libsoundio.dylib + + + PreserveNewest + libsoundio.so + + + + diff --git a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoAudioBuffer.cs b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoAudioBuffer.cs new file mode 100644 index 00000000..7f32b953 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoAudioBuffer.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Audio.Backends.SoundIo +{ + class SoundIoAudioBuffer + { + public readonly ulong DriverIdentifier; + public readonly ulong SampleCount; + public ulong SamplePlayed; + + public SoundIoAudioBuffer(ulong driverIdentifier, ulong sampleCount) + { + DriverIdentifier = driverIdentifier; + SampleCount = sampleCount; + SamplePlayed = 0; + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs new file mode 100644 index 00000000..02da2776 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs @@ -0,0 +1,247 @@ +using Ryujinx.Audio.Backends.SoundIo.Native; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System; +using System.Collections.Concurrent; +using System.Threading; +using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo; +using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; + +namespace Ryujinx.Audio.Backends.SoundIo +{ + public class SoundIoHardwareDeviceDriver : IHardwareDeviceDriver + { + private readonly SoundIoContext _audioContext; + private readonly SoundIoDeviceContext _audioDevice; + private readonly ManualResetEvent _updateRequiredEvent; + private readonly ManualResetEvent _pauseEvent; + private readonly ConcurrentDictionary _sessions; + private int _disposeState; + + public SoundIoHardwareDeviceDriver() + { + _audioContext = SoundIoContext.Create(); + _updateRequiredEvent = new ManualResetEvent(false); + _pauseEvent = new ManualResetEvent(true); + _sessions = new ConcurrentDictionary(); + + _audioContext.Connect(); + _audioContext.FlushEvents(); + + _audioDevice = FindValidAudioDevice(_audioContext, true); + } + + public static bool IsSupported => IsSupportedInternal(); + + private static bool IsSupportedInternal() + { + SoundIoContext context = null; + SoundIoDeviceContext device = null; + SoundIoOutStreamContext stream = null; + + bool backendDisconnected = false; + + try + { + context = SoundIoContext.Create(); + context.OnBackendDisconnect = err => + { + backendDisconnected = true; + }; + + context.Connect(); + context.FlushEvents(); + + if (backendDisconnected) + { + return false; + } + + if (context.OutputDeviceCount == 0) + { + return false; + } + + device = FindValidAudioDevice(context); + + if (device == null || backendDisconnected) + { + return false; + } + + stream = device.CreateOutStream(); + + if (stream == null || backendDisconnected) + { + return false; + } + + return true; + } + catch + { + return false; + } + finally + { + stream?.Dispose(); + context?.Dispose(); + } + } + + private static SoundIoDeviceContext FindValidAudioDevice(SoundIoContext audioContext, bool fallback = false) + { + SoundIoDeviceContext defaultAudioDevice = audioContext.GetOutputDevice(audioContext.DefaultOutputDeviceIndex); + + if (!defaultAudioDevice.IsRaw) + { + return defaultAudioDevice; + } + + for (int i = 0; i < audioContext.OutputDeviceCount; i++) + { + SoundIoDeviceContext audioDevice = audioContext.GetOutputDevice(i); + + if (audioDevice.Id == defaultAudioDevice.Id && !audioDevice.IsRaw) + { + return audioDevice; + } + } + + return fallback ? defaultAudioDevice : null; + } + + public ManualResetEvent GetUpdateRequiredEvent() + { + return _updateRequiredEvent; + } + + public ManualResetEvent GetPauseEvent() + { + return _pauseEvent; + } + + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume) + { + if (channelCount == 0) + { + channelCount = 2; + } + + if (sampleRate == 0) + { + sampleRate = Constants.TargetSampleRate; + } + + volume = Math.Clamp(volume, 0, 1); + + if (direction != Direction.Output) + { + throw new NotImplementedException("Input direction is currently not implemented on SoundIO backend!"); + } + + SoundIoHardwareDeviceSession session = new SoundIoHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount, volume); + + _sessions.TryAdd(session, 0); + + return session; + } + + internal bool Unregister(SoundIoHardwareDeviceSession session) + { + return _sessions.TryRemove(session, out _); + } + + public static SoundIoFormat GetSoundIoFormat(SampleFormat format) + { + return format switch + { + SampleFormat.PcmInt8 => SoundIoFormat.S8, + SampleFormat.PcmInt16 => SoundIoFormat.S16LE, + SampleFormat.PcmInt24 => SoundIoFormat.S24LE, + SampleFormat.PcmInt32 => SoundIoFormat.S32LE, + SampleFormat.PcmFloat => SoundIoFormat.Float32LE, + _ => throw new ArgumentException ($"Unsupported sample format {format}"), + }; + } + + internal SoundIoOutStreamContext OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) + { + SoundIoFormat driverSampleFormat = GetSoundIoFormat(requestedSampleFormat); + + if (!_audioDevice.SupportsSampleRate((int)requestedSampleRate)) + { + throw new ArgumentException($"This sound device does not support a sample rate of {requestedSampleRate}Hz"); + } + + if (!_audioDevice.SupportsFormat(driverSampleFormat)) + { + throw new ArgumentException($"This sound device does not support {requestedSampleFormat}"); + } + + if (!_audioDevice.SupportsChannelCount((int)requestedChannelCount)) + { + throw new ArgumentException($"This sound device does not support channel count {requestedChannelCount}"); + } + + SoundIoOutStreamContext result = _audioDevice.CreateOutStream(); + + result.Name = "Ryujinx"; + result.Layout = SoundIoChannelLayout.GetDefaultValue((int)requestedChannelCount); + result.Format = driverSampleFormat; + result.SampleRate = (int)requestedSampleRate; + + return result; + } + + internal void FlushContextEvents() + { + _audioContext.FlushEvents(); + } + + public void Dispose() + { + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + foreach (SoundIoHardwareDeviceSession session in _sessions.Keys) + { + session.Dispose(); + } + + _audioContext.Disconnect(); + _audioContext.Dispose(); + _pauseEvent.Dispose(); + } + } + + public bool SupportsSampleRate(uint sampleRate) + { + return _audioDevice.SupportsSampleRate((int)sampleRate); + } + + public bool SupportsSampleFormat(SampleFormat sampleFormat) + { + return _audioDevice.SupportsFormat(GetSoundIoFormat(sampleFormat)); + } + + public bool SupportsChannelCount(uint channelCount) + { + return _audioDevice.SupportsChannelCount((int)channelCount); + } + + public bool SupportsDirection(Direction direction) + { + // TODO: add direction input when supported. + return direction == Direction.Output; + } + } +} diff --git a/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs new file mode 100644 index 00000000..96d9ce97 --- /dev/null +++ b/src/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs @@ -0,0 +1,438 @@ +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Backends.SoundIo.Native; +using Ryujinx.Audio.Common; +using Ryujinx.Memory; +using System; +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; +using System.Threading; +using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo; + +namespace Ryujinx.Audio.Backends.SoundIo +{ + class SoundIoHardwareDeviceSession : HardwareDeviceSessionOutputBase + { + private SoundIoHardwareDeviceDriver _driver; + private ConcurrentQueue _queuedBuffers; + private SoundIoOutStreamContext _outputStream; + private DynamicRingBuffer _ringBuffer; + private ulong _playedSampleCount; + private ManualResetEvent _updateRequiredEvent; + private int _disposeState; + + public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + { + _driver = driver; + _updateRequiredEvent = _driver.GetUpdateRequiredEvent(); + _queuedBuffers = new ConcurrentQueue(); + _ringBuffer = new DynamicRingBuffer(); + + SetupOutputStream(requestedVolume); + } + + private void SetupOutputStream(float requestedVolume) + { + _outputStream = _driver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount); + _outputStream.WriteCallback += Update; + _outputStream.Volume = requestedVolume; + // TODO: Setup other callbacks (errors, ect). + + _outputStream.Open(); + } + + public override ulong GetPlayedSampleCount() + { + return Interlocked.Read(ref _playedSampleCount); + } + + public override float GetVolume() + { + return _outputStream.Volume; + } + + public override void PrepareToClose() { } + + public override void QueueBuffer(AudioBuffer buffer) + { + SoundIoAudioBuffer driverBuffer = new SoundIoAudioBuffer(buffer.DataPointer, GetSampleCount(buffer)); + + _ringBuffer.Write(buffer.Data, 0, buffer.Data.Length); + + _queuedBuffers.Enqueue(driverBuffer); + } + + public override void SetVolume(float volume) + { + _outputStream.SetVolume(volume); + } + + public override void Start() + { + _outputStream.Start(); + _outputStream.Pause(false); + + _driver.FlushContextEvents(); + } + + public override void Stop() + { + _outputStream.Pause(true); + + _driver.FlushContextEvents(); + } + + public override void UnregisterBuffer(AudioBuffer buffer) {} + + public override bool WasBufferFullyConsumed(AudioBuffer buffer) + { + if (!_queuedBuffers.TryPeek(out SoundIoAudioBuffer driverBuffer)) + { + return true; + } + + return driverBuffer.DriverIdentifier != buffer.DataPointer; + } + + private unsafe void Update(int minFrameCount, int maxFrameCount) + { + int bytesPerFrame = _outputStream.BytesPerFrame; + uint bytesPerSample = (uint)_outputStream.BytesPerSample; + + int bufferedFrames = _ringBuffer.Length / bytesPerFrame; + + int frameCount = Math.Min(bufferedFrames, maxFrameCount); + + if (frameCount == 0) + { + return; + } + + Span areas = _outputStream.BeginWrite(ref frameCount); + + int channelCount = areas.Length; + + byte[] samples = new byte[frameCount * bytesPerFrame]; + + _ringBuffer.Read(samples, 0, samples.Length); + + // This is a huge ugly block of code, but we save + // a significant amount of time over the generic + // loop that handles other channel counts. + // TODO: Is this still right in 2022? + + // Mono + if (channelCount == 1) + { + ref SoundIoChannelArea area = ref areas[0]; + + fixed (byte* srcptr = samples) + { + if (bytesPerSample == 1) + { + for (int frame = 0; frame < frameCount; frame++) + { + ((byte*)area.Pointer)[0] = srcptr[frame * bytesPerFrame]; + + area.Pointer += area.Step; + } + } + else if (bytesPerSample == 2) + { + for (int frame = 0; frame < frameCount; frame++) + { + ((short*)area.Pointer)[0] = ((short*)srcptr)[frame * bytesPerFrame >> 1]; + + area.Pointer += area.Step; + } + } + else if (bytesPerSample == 4) + { + for (int frame = 0; frame < frameCount; frame++) + { + ((int*)area.Pointer)[0] = ((int*)srcptr)[frame * bytesPerFrame >> 2]; + + area.Pointer += area.Step; + } + } + else + { + for (int frame = 0; frame < frameCount; frame++) + { + Unsafe.CopyBlockUnaligned((byte*)area.Pointer, srcptr + (frame * bytesPerFrame), bytesPerSample); + + area.Pointer += area.Step; + } + } + } + } + // Stereo + else if (channelCount == 2) + { + ref SoundIoChannelArea area1 = ref areas[0]; + ref SoundIoChannelArea area2 = ref areas[1]; + + fixed (byte* srcptr = samples) + { + if (bytesPerSample == 1) + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + ((byte*)area1.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 0]; + + // Channel 2 + ((byte*)area2.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 1]; + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + } + } + else if (bytesPerSample == 2) + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + ((short*)area1.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 0]; + + // Channel 2 + ((short*)area2.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 1]; + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + } + } + else if (bytesPerSample == 4) + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + ((int*)area1.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 0]; + + // Channel 2 + ((int*)area2.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 1]; + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + } + } + else + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + Unsafe.CopyBlockUnaligned((byte*)area1.Pointer, srcptr + (frame * bytesPerFrame) + (0 * bytesPerSample), bytesPerSample); + + // Channel 2 + Unsafe.CopyBlockUnaligned((byte*)area2.Pointer, srcptr + (frame * bytesPerFrame) + (1 * bytesPerSample), bytesPerSample); + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + } + } + } + } + // Surround + else if (channelCount == 6) + { + ref SoundIoChannelArea area1 = ref areas[0]; + ref SoundIoChannelArea area2 = ref areas[1]; + ref SoundIoChannelArea area3 = ref areas[2]; + ref SoundIoChannelArea area4 = ref areas[3]; + ref SoundIoChannelArea area5 = ref areas[4]; + ref SoundIoChannelArea area6 = ref areas[5]; + + fixed (byte* srcptr = samples) + { + if (bytesPerSample == 1) + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + ((byte*)area1.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 0]; + + // Channel 2 + ((byte*)area2.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 1]; + + // Channel 3 + ((byte*)area3.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 2]; + + // Channel 4 + ((byte*)area4.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 3]; + + // Channel 5 + ((byte*)area5.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 4]; + + // Channel 6 + ((byte*)area6.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 5]; + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + area3.Pointer += area3.Step; + area4.Pointer += area4.Step; + area5.Pointer += area5.Step; + area6.Pointer += area6.Step; + } + } + else if (bytesPerSample == 2) + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + ((short*)area1.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 0]; + + // Channel 2 + ((short*)area2.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 1]; + + // Channel 3 + ((short*)area3.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 2]; + + // Channel 4 + ((short*)area4.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 3]; + + // Channel 5 + ((short*)area5.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 4]; + + // Channel 6 + ((short*)area6.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 5]; + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + area3.Pointer += area3.Step; + area4.Pointer += area4.Step; + area5.Pointer += area5.Step; + area6.Pointer += area6.Step; + } + } + else if (bytesPerSample == 4) + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + ((int*)area1.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 0]; + + // Channel 2 + ((int*)area2.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 1]; + + // Channel 3 + ((int*)area3.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 2]; + + // Channel 4 + ((int*)area4.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 3]; + + // Channel 5 + ((int*)area5.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 4]; + + // Channel 6 + ((int*)area6.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 5]; + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + area3.Pointer += area3.Step; + area4.Pointer += area4.Step; + area5.Pointer += area5.Step; + area6.Pointer += area6.Step; + } + } + else + { + for (int frame = 0; frame < frameCount; frame++) + { + // Channel 1 + Unsafe.CopyBlockUnaligned((byte*)area1.Pointer, srcptr + (frame * bytesPerFrame) + (0 * bytesPerSample), bytesPerSample); + + // Channel 2 + Unsafe.CopyBlockUnaligned((byte*)area2.Pointer, srcptr + (frame * bytesPerFrame) + (1 * bytesPerSample), bytesPerSample); + + // Channel 3 + Unsafe.CopyBlockUnaligned((byte*)area3.Pointer, srcptr + (frame * bytesPerFrame) + (2 * bytesPerSample), bytesPerSample); + + // Channel 4 + Unsafe.CopyBlockUnaligned((byte*)area4.Pointer, srcptr + (frame * bytesPerFrame) + (3 * bytesPerSample), bytesPerSample); + + // Channel 5 + Unsafe.CopyBlockUnaligned((byte*)area5.Pointer, srcptr + (frame * bytesPerFrame) + (4 * bytesPerSample), bytesPerSample); + + // Channel 6 + Unsafe.CopyBlockUnaligned((byte*)area6.Pointer, srcptr + (frame * bytesPerFrame) + (5 * bytesPerSample), bytesPerSample); + + area1.Pointer += area1.Step; + area2.Pointer += area2.Step; + area3.Pointer += area3.Step; + area4.Pointer += area4.Step; + area5.Pointer += area5.Step; + area6.Pointer += area6.Step; + } + } + } + } + // Every other channel count + else + { + fixed (byte* srcptr = samples) + { + for (int frame = 0; frame < frameCount; frame++) + { + for (int channel = 0; channel < areas.Length; channel++) + { + // Copy channel by channel, frame by frame. This is slow! + Unsafe.CopyBlockUnaligned((byte*)areas[channel].Pointer, srcptr + (frame * bytesPerFrame) + (channel * bytesPerSample), bytesPerSample); + + areas[channel].Pointer += areas[channel].Step; + } + } + } + } + + _outputStream.EndWrite(); + + ulong sampleCount = (ulong)(samples.Length / bytesPerSample / channelCount); + + ulong availaibleSampleCount = sampleCount; + + bool needUpdate = false; + + while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SoundIoAudioBuffer driverBuffer)) + { + ulong sampleStillNeeded = driverBuffer.SampleCount - Interlocked.Read(ref driverBuffer.SamplePlayed); + ulong playedAudioBufferSampleCount = Math.Min(sampleStillNeeded, availaibleSampleCount); + + Interlocked.Add(ref driverBuffer.SamplePlayed, playedAudioBufferSampleCount); + availaibleSampleCount -= playedAudioBufferSampleCount; + + if (Interlocked.Read(ref driverBuffer.SamplePlayed) == driverBuffer.SampleCount) + { + _queuedBuffers.TryDequeue(out _); + + needUpdate = true; + } + + Interlocked.Add(ref _playedSampleCount, playedAudioBufferSampleCount); + } + + // Notify the output if needed. + if (needUpdate) + { + _updateRequiredEvent.Set(); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && _driver.Unregister(this)) + { + PrepareToClose(); + Stop(); + + _outputStream.Dispose(); + } + } + + public override void Dispose() + { + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + } +} diff --git a/src/Ryujinx.Audio/AudioManager.cs b/src/Ryujinx.Audio/AudioManager.cs new file mode 100644 index 00000000..c37ca4a3 --- /dev/null +++ b/src/Ryujinx.Audio/AudioManager.cs @@ -0,0 +1,132 @@ +using System; +using System.Threading; + +namespace Ryujinx.Audio +{ + /// + /// Manage audio input and output system. + /// + public class AudioManager : IDisposable + { + /// + /// Lock used to control the waiters registration. + /// + private object _lock = new object(); + + /// + /// Events signaled when the driver played audio buffers. + /// + private ManualResetEvent[] _updateRequiredEvents; + + /// + /// Action to execute when the driver played audio buffers. + /// + private Action[] _actions; + + /// + /// The worker thread in charge of handling sessions update. + /// + private Thread _workerThread; + + private bool _isRunning; + + /// + /// Create a new . + /// + public AudioManager() + { + _updateRequiredEvents = new ManualResetEvent[2]; + _actions = new Action[2]; + _isRunning = false; + + // Termination event. + _updateRequiredEvents[1] = new ManualResetEvent(false); + + _workerThread = new Thread(Update) + { + Name = "AudioManager.Worker" + }; + } + + /// + /// Start the . + /// + public void Start() + { + if (_workerThread.IsAlive) + { + throw new InvalidOperationException(); + } + + _isRunning = true; + _workerThread.Start(); + } + + /// + /// Initialize update handlers. + /// + /// The driver event that will get signaled by the device driver when an audio buffer finished playing/being captured + /// The callback to call when an audio buffer finished playing + /// The callback to call when an audio buffer was captured + public void Initialize(ManualResetEvent updatedRequiredEvent, Action outputCallback, Action inputCallback) + { + lock (_lock) + { + _updateRequiredEvents[0] = updatedRequiredEvent; + _actions[0] = outputCallback; + _actions[1] = inputCallback; + } + } + + /// + /// Entrypoint of the in charge of updating the . + /// + private void Update() + { + while (_isRunning) + { + int index = WaitHandle.WaitAny(_updateRequiredEvents); + + // Last index is here to indicate thread termination. + if (index + 1 == _updateRequiredEvents.Length) + { + break; + } + + lock (_lock) + { + foreach (Action action in _actions) + { + action?.Invoke(); + } + + _updateRequiredEvents[0].Reset(); + } + } + } + + /// + /// Stop updating the without stopping the worker thread. + /// + public void StopUpdates() + { + _isRunning = false; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _updateRequiredEvents[1].Set(); + _workerThread.Join(); + + _updateRequiredEvents[1].Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Backends/Common/BackendHelper.cs b/src/Ryujinx.Audio/Backends/Common/BackendHelper.cs new file mode 100644 index 00000000..30db340f --- /dev/null +++ b/src/Ryujinx.Audio/Backends/Common/BackendHelper.cs @@ -0,0 +1,26 @@ +using Ryujinx.Audio.Common; +using System; + +namespace Ryujinx.Audio.Backends.Common +{ + public static class BackendHelper + { + public static int GetSampleSize(SampleFormat format) + { + return format switch + { + SampleFormat.PcmInt8 => sizeof(byte), + SampleFormat.PcmInt16 => sizeof(ushort), + SampleFormat.PcmInt24 => 3, + SampleFormat.PcmInt32 => sizeof(int), + SampleFormat.PcmFloat => sizeof(float), + _ => throw new ArgumentException($"{format}"), + }; + } + + public static int GetSampleCount(SampleFormat format, int channelCount, int bufferSize) + { + return bufferSize / GetSampleSize(format) / channelCount; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs b/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs new file mode 100644 index 00000000..9bf20d4b --- /dev/null +++ b/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs @@ -0,0 +1,166 @@ +using Ryujinx.Common; +using System; + +namespace Ryujinx.Audio.Backends.Common +{ + /// + /// A ring buffer that grow if data written to it is too big to fit. + /// + public class DynamicRingBuffer + { + private const int RingBufferAlignment = 2048; + + private object _lock = new object(); + + private byte[] _buffer; + private int _size; + private int _headOffset; + private int _tailOffset; + + public int Length => _size; + + public DynamicRingBuffer(int initialCapacity = RingBufferAlignment) + { + _buffer = new byte[initialCapacity]; + } + + public void Clear() + { + _size = 0; + _headOffset = 0; + _tailOffset = 0; + } + + public void Clear(int size) + { + lock (_lock) + { + if (size > _size) + { + size = _size; + } + + if (size == 0) + { + return; + } + + _headOffset = (_headOffset + size) % _buffer.Length; + _size -= size; + + if (_size == 0) + { + _headOffset = 0; + _tailOffset = 0; + } + } + } + + private void SetCapacityLocked(int capacity) + { + byte[] buffer = new byte[capacity]; + + if (_size > 0) + { + if (_headOffset < _tailOffset) + { + Buffer.BlockCopy(_buffer, _headOffset, buffer, 0, _size); + } + else + { + Buffer.BlockCopy(_buffer, _headOffset, buffer, 0, _buffer.Length - _headOffset); + Buffer.BlockCopy(_buffer, 0, buffer, _buffer.Length - _headOffset, _tailOffset); + } + } + + _buffer = buffer; + _headOffset = 0; + _tailOffset = _size; + } + + + public void Write(T[] buffer, int index, int count) + { + if (count == 0) + { + return; + } + + lock (_lock) + { + if ((_size + count) > _buffer.Length) + { + SetCapacityLocked(BitUtils.AlignUp(_size + count, RingBufferAlignment)); + } + + if (_headOffset < _tailOffset) + { + int tailLength = _buffer.Length - _tailOffset; + + if (tailLength >= count) + { + Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, count); + } + else + { + Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, tailLength); + Buffer.BlockCopy(buffer, index + tailLength, _buffer, 0, count - tailLength); + } + } + else + { + Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, count); + } + + _size += count; + _tailOffset = (_tailOffset + count) % _buffer.Length; + } + } + + public int Read(T[] buffer, int index, int count) + { + lock (_lock) + { + if (count > _size) + { + count = _size; + } + + if (count == 0) + { + return 0; + } + + if (_headOffset < _tailOffset) + { + Buffer.BlockCopy(_buffer, _headOffset, buffer, index, count); + } + else + { + int tailLength = _buffer.Length - _headOffset; + + if (tailLength >= count) + { + Buffer.BlockCopy(_buffer, _headOffset, buffer, index, count); + } + else + { + Buffer.BlockCopy(_buffer, _headOffset, buffer, index, tailLength); + Buffer.BlockCopy(_buffer, 0, buffer, index + tailLength, count - tailLength); + } + } + + _size -= count; + _headOffset = (_headOffset + count) % _buffer.Length; + + if (_size == 0) + { + _headOffset = 0; + _tailOffset = 0; + } + + return count; + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Backends/Common/HardwareDeviceSessionOutputBase.cs b/src/Ryujinx.Audio/Backends/Common/HardwareDeviceSessionOutputBase.cs new file mode 100644 index 00000000..6fb3bee0 --- /dev/null +++ b/src/Ryujinx.Audio/Backends/Common/HardwareDeviceSessionOutputBase.cs @@ -0,0 +1,79 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Backends.Common +{ + public abstract class HardwareDeviceSessionOutputBase : IHardwareDeviceSession + { + public IVirtualMemoryManager MemoryManager { get; } + public SampleFormat RequestedSampleFormat { get; } + public uint RequestedSampleRate { get; } + public uint RequestedChannelCount { get; } + + public HardwareDeviceSessionOutputBase(IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) + { + MemoryManager = memoryManager; + RequestedSampleFormat = requestedSampleFormat; + RequestedSampleRate = requestedSampleRate; + RequestedChannelCount = requestedChannelCount; + } + + private byte[] GetBufferSamples(AudioBuffer buffer) + { + if (buffer.DataPointer == 0) + { + return null; + } + + byte[] data = new byte[buffer.DataSize]; + + MemoryManager.Read(buffer.DataPointer, data); + + return data; + } + + protected ulong GetSampleCount(AudioBuffer buffer) + { + return GetSampleCount((int)buffer.DataSize); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected ulong GetSampleCount(int dataSize) + { + return (ulong)BackendHelper.GetSampleCount(RequestedSampleFormat, (int)RequestedChannelCount, dataSize); + } + + public abstract void Dispose(); + public abstract void PrepareToClose(); + public abstract void QueueBuffer(AudioBuffer buffer); + public abstract void SetVolume(float volume); + public abstract float GetVolume(); + public abstract void Start(); + public abstract void Stop(); + public abstract ulong GetPlayedSampleCount(); + public abstract bool WasBufferFullyConsumed(AudioBuffer buffer); + public virtual bool RegisterBuffer(AudioBuffer buffer) + { + return RegisterBuffer(buffer, GetBufferSamples(buffer)); + } + + public virtual bool RegisterBuffer(AudioBuffer buffer, byte[] samples) + { + if (samples == null) + { + return false; + } + + if (buffer.Data == null) + { + buffer.Data = samples; + } + + return true; + } + + public virtual void UnregisterBuffer(AudioBuffer buffer) { } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs b/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs new file mode 100644 index 00000000..22919f1e --- /dev/null +++ b/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs @@ -0,0 +1,186 @@ +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Backends.Dummy; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Threading; + +using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; + +namespace Ryujinx.Audio.Backends.CompatLayer +{ + public class CompatLayerHardwareDeviceDriver : IHardwareDeviceDriver + { + private IHardwareDeviceDriver _realDriver; + + public static bool IsSupported => true; + + public CompatLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice) + { + _realDriver = realDevice; + } + + public void Dispose() + { + _realDriver.Dispose(); + } + + public ManualResetEvent GetUpdateRequiredEvent() + { + return _realDriver.GetUpdateRequiredEvent(); + } + + public ManualResetEvent GetPauseEvent() + { + return _realDriver.GetPauseEvent(); + } + + private uint SelectHardwareChannelCount(uint targetChannelCount) + { + if (_realDriver.SupportsChannelCount(targetChannelCount)) + { + return targetChannelCount; + } + + return targetChannelCount switch + { + 6 => SelectHardwareChannelCount(2), + 2 => SelectHardwareChannelCount(1), + 1 => throw new ArgumentException("No valid channel configuration found!"), + _ => throw new ArgumentException($"Invalid targetChannelCount {targetChannelCount}") + }; + } + + private SampleFormat SelectHardwareSampleFormat(SampleFormat targetSampleFormat) + { + if (_realDriver.SupportsSampleFormat(targetSampleFormat)) + { + return targetSampleFormat; + } + + // Attempt conversion from PCM16. + if (targetSampleFormat == SampleFormat.PcmInt16) + { + // Prefer PCM32 if we need to convert. + if (_realDriver.SupportsSampleFormat(SampleFormat.PcmInt32)) + { + return SampleFormat.PcmInt32; + } + + // If not supported, PCM float provides the best quality with a cost lower than PCM24. + if (_realDriver.SupportsSampleFormat(SampleFormat.PcmFloat)) + { + return SampleFormat.PcmFloat; + } + + if (_realDriver.SupportsSampleFormat(SampleFormat.PcmInt24)) + { + return SampleFormat.PcmInt24; + } + + // If nothing is truly supported, attempt PCM8 at the cost of losing quality. + if (_realDriver.SupportsSampleFormat(SampleFormat.PcmInt8)) + { + return SampleFormat.PcmInt8; + } + } + + throw new ArgumentException("No valid sample format configuration found!"); + } + + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume) + { + if (channelCount == 0) + { + channelCount = 2; + } + + if (sampleRate == 0) + { + sampleRate = Constants.TargetSampleRate; + } + + volume = Math.Clamp(volume, 0, 1); + + if (!_realDriver.SupportsDirection(direction)) + { + if (direction == Direction.Input) + { + Logger.Warning?.Print(LogClass.Audio, "The selected audio backend doesn't support audio input, fallback to dummy..."); + + return new DummyHardwareDeviceSessionInput(this, memoryManager, sampleFormat, sampleRate, channelCount); + } + + throw new NotImplementedException(); + } + + SampleFormat hardwareSampleFormat = SelectHardwareSampleFormat(sampleFormat); + uint hardwareChannelCount = SelectHardwareChannelCount(channelCount); + + IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, hardwareSampleFormat, sampleRate, hardwareChannelCount, volume); + + if (hardwareChannelCount == channelCount && hardwareSampleFormat == sampleFormat) + { + return realSession; + } + + if (hardwareSampleFormat != sampleFormat) + { + Logger.Warning?.Print(LogClass.Audio, $"{sampleFormat} isn't supported by the audio device, conversion to {hardwareSampleFormat} will happen."); + + if (hardwareSampleFormat < sampleFormat) + { + Logger.Warning?.Print(LogClass.Audio, $"{hardwareSampleFormat} has lower quality than {sampleFormat}, expect some loss in audio fidelity."); + } + } + + if (direction == Direction.Input) + { + Logger.Warning?.Print(LogClass.Audio, $"The selected audio backend doesn't support the requested audio input configuration, fallback to dummy..."); + + // TODO: We currently don't support audio input upsampling/downsampling, implement this. + realSession.Dispose(); + + return new DummyHardwareDeviceSessionInput(this, memoryManager, sampleFormat, sampleRate, channelCount); + } + + // It must be a HardwareDeviceSessionOutputBase. + if (realSession is not HardwareDeviceSessionOutputBase realSessionOutputBase) + { + throw new InvalidOperationException($"Real driver session class type isn't based on {typeof(HardwareDeviceSessionOutputBase).Name}."); + } + + // If we need to do post processing before sending to the hardware device, wrap around it. + return new CompatLayerHardwareDeviceSession(realSessionOutputBase, sampleFormat, channelCount); + } + + public bool SupportsChannelCount(uint channelCount) + { + return channelCount == 1 || channelCount == 2 || channelCount == 6; + } + + public bool SupportsSampleFormat(SampleFormat sampleFormat) + { + // TODO: More formats. + return sampleFormat == SampleFormat.PcmInt16; + } + + public bool SupportsSampleRate(uint sampleRate) + { + // TODO: More sample rates. + return sampleRate == Constants.TargetSampleRate; + } + + public IHardwareDeviceDriver GetRealDeviceDriver() + { + return _realDriver; + } + + public bool SupportsDirection(Direction direction) + { + return direction == Direction.Input || direction == Direction.Output; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceSession.cs b/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceSession.cs new file mode 100644 index 00000000..f22a7a69 --- /dev/null +++ b/src/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceSession.cs @@ -0,0 +1,162 @@ +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Dsp; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Backends.CompatLayer +{ + class CompatLayerHardwareDeviceSession : HardwareDeviceSessionOutputBase + { + private HardwareDeviceSessionOutputBase _realSession; + private SampleFormat _userSampleFormat; + private uint _userChannelCount; + + public CompatLayerHardwareDeviceSession(HardwareDeviceSessionOutputBase realSession, SampleFormat userSampleFormat, uint userChannelCount) : base(realSession.MemoryManager, realSession.RequestedSampleFormat, realSession.RequestedSampleRate, userChannelCount) + { + _realSession = realSession; + _userSampleFormat = userSampleFormat; + _userChannelCount = userChannelCount; + } + + public override void Dispose() + { + _realSession.Dispose(); + } + + public override ulong GetPlayedSampleCount() + { + return _realSession.GetPlayedSampleCount(); + } + + public override float GetVolume() + { + return _realSession.GetVolume(); + } + + public override void PrepareToClose() + { + _realSession.PrepareToClose(); + } + + public override void QueueBuffer(AudioBuffer buffer) + { + SampleFormat realSampleFormat = _realSession.RequestedSampleFormat; + + if (_userSampleFormat != realSampleFormat) + { + if (_userSampleFormat != SampleFormat.PcmInt16) + { + throw new NotImplementedException("Converting formats other than PCM16 is not supported."); + } + + int userSampleCount = buffer.Data.Length / BackendHelper.GetSampleSize(_userSampleFormat); + + ReadOnlySpan samples = MemoryMarshal.Cast(buffer.Data); + byte[] convertedSamples = new byte[BackendHelper.GetSampleSize(realSampleFormat) * userSampleCount]; + + switch (realSampleFormat) + { + case SampleFormat.PcmInt8: + PcmHelper.ConvertSampleToPcm8(MemoryMarshal.Cast(convertedSamples), samples); + break; + case SampleFormat.PcmInt24: + PcmHelper.ConvertSampleToPcm24(convertedSamples, samples); + break; + case SampleFormat.PcmInt32: + PcmHelper.ConvertSampleToPcm32(MemoryMarshal.Cast(convertedSamples), samples); + break; + case SampleFormat.PcmFloat: + PcmHelper.ConvertSampleToPcmFloat(MemoryMarshal.Cast(convertedSamples), samples); + break; + default: + throw new NotImplementedException($"Sample format conversion from {_userSampleFormat} to {realSampleFormat} not implemented."); + } + + buffer.Data = convertedSamples; + } + + _realSession.QueueBuffer(buffer); + } + + public override bool RegisterBuffer(AudioBuffer buffer, byte[] samples) + { + if (samples == null) + { + return false; + } + + if (_userChannelCount != _realSession.RequestedChannelCount) + { + if (_userSampleFormat != SampleFormat.PcmInt16) + { + throw new NotImplementedException("Downmixing formats other than PCM16 is not supported."); + } + + ReadOnlySpan samplesPCM16 = MemoryMarshal.Cast(samples); + + if (_userChannelCount == 6) + { + samplesPCM16 = Downmixing.DownMixSurroundToStereo(samplesPCM16); + + if (_realSession.RequestedChannelCount == 1) + { + samplesPCM16 = Downmixing.DownMixStereoToMono(samplesPCM16); + } + } + else if (_userChannelCount == 2 && _realSession.RequestedChannelCount == 1) + { + samplesPCM16 = Downmixing.DownMixStereoToMono(samplesPCM16); + } + else + { + throw new NotImplementedException($"Downmixing from {_userChannelCount} to {_realSession.RequestedChannelCount} not implemented."); + } + + samples = MemoryMarshal.Cast(samplesPCM16).ToArray(); + } + + AudioBuffer fakeBuffer = new AudioBuffer + { + BufferTag = buffer.BufferTag, + DataPointer = buffer.DataPointer, + DataSize = (ulong)samples.Length + }; + + bool result = _realSession.RegisterBuffer(fakeBuffer, samples); + + if (result) + { + buffer.Data = fakeBuffer.Data; + buffer.DataSize = fakeBuffer.DataSize; + } + + return result; + } + + public override void SetVolume(float volume) + { + _realSession.SetVolume(volume); + } + + public override void Start() + { + _realSession.Start(); + } + + public override void Stop() + { + _realSession.Stop(); + } + + public override void UnregisterBuffer(AudioBuffer buffer) + { + _realSession.UnregisterBuffer(buffer); + } + + public override bool WasBufferFullyConsumed(AudioBuffer buffer) + { + return _realSession.WasBufferFullyConsumed(buffer); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Backends/CompatLayer/Downmixing.cs b/src/Ryujinx.Audio/Backends/CompatLayer/Downmixing.cs new file mode 100644 index 00000000..6959c158 --- /dev/null +++ b/src/Ryujinx.Audio/Backends/CompatLayer/Downmixing.cs @@ -0,0 +1,125 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Backends.CompatLayer +{ + public static class Downmixing + { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct Channel51FormatPCM16 + { + public short FrontLeft; + public short FrontRight; + public short FrontCenter; + public short LowFrequency; + public short BackLeft; + public short BackRight; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct ChannelStereoFormatPCM16 + { + public short Left; + public short Right; + } + + private const int Q15Bits = 16; + private const int RawQ15One = 1 << Q15Bits; + private const int RawQ15HalfOne = (int)(0.5f * RawQ15One); + private const int Minus3dBInQ15 = (int)(0.707f * RawQ15One); + private const int Minus6dBInQ15 = (int)(0.501f * RawQ15One); + private const int Minus12dBInQ15 = (int)(0.251f * RawQ15One); + + private static readonly int[] DefaultSurroundToStereoCoefficients = new int[4] + { + RawQ15One, + Minus3dBInQ15, + Minus12dBInQ15, + Minus3dBInQ15 + }; + + private static readonly int[] DefaultStereoToMonoCoefficients = new int[2] + { + Minus6dBInQ15, + Minus6dBInQ15 + }; + + private const int SurroundChannelCount = 6; + private const int StereoChannelCount = 2; + private const int MonoChannelCount = 1; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ReadOnlySpan GetSurroundBuffer(ReadOnlySpan data) + { + return MemoryMarshal.Cast(data); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ReadOnlySpan GetStereoBuffer(ReadOnlySpan data) + { + return MemoryMarshal.Cast(data); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static short DownMixStereoToMono(ReadOnlySpan coefficients, short left, short right) + { + return (short)((left * coefficients[0] + right * coefficients[1]) >> Q15Bits); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static short DownMixSurroundToStereo(ReadOnlySpan coefficients, short back, short lfe, short center, short front) + { + return (short)((coefficients[3] * back + coefficients[2] * lfe + coefficients[1] * center + coefficients[0] * front + RawQ15HalfOne) >> Q15Bits); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static short[] DownMixSurroundToStereo(ReadOnlySpan coefficients, ReadOnlySpan data) + { + int samplePerChannelCount = data.Length / SurroundChannelCount; + + short[] downmixedBuffer = new short[samplePerChannelCount * StereoChannelCount]; + + ReadOnlySpan channels = GetSurroundBuffer(data); + + for (int i = 0; i < samplePerChannelCount; i++) + { + Channel51FormatPCM16 channel = channels[i]; + + downmixedBuffer[i * 2] = DownMixSurroundToStereo(coefficients, channel.BackLeft, channel.LowFrequency, channel.FrontCenter, channel.FrontLeft); + downmixedBuffer[i * 2 + 1] = DownMixSurroundToStereo(coefficients, channel.BackRight, channel.LowFrequency, channel.FrontCenter, channel.FrontRight); + } + + return downmixedBuffer; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static short[] DownMixStereoToMono(ReadOnlySpan coefficients, ReadOnlySpan data) + { + int samplePerChannelCount = data.Length / StereoChannelCount; + + short[] downmixedBuffer = new short[samplePerChannelCount * MonoChannelCount]; + + ReadOnlySpan channels = GetStereoBuffer(data); + + for (int i = 0; i < samplePerChannelCount; i++) + { + ChannelStereoFormatPCM16 channel = channels[i]; + + downmixedBuffer[i] = DownMixStereoToMono(coefficients, channel.Left, channel.Right); + } + + return downmixedBuffer; + } + + public static short[] DownMixStereoToMono(ReadOnlySpan data) + { + return DownMixStereoToMono(DefaultStereoToMonoCoefficients, data); + } + + public static short[] DownMixSurroundToStereo(ReadOnlySpan data) + { + return DownMixSurroundToStereo(DefaultSurroundToStereoCoefficients, data); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs new file mode 100644 index 00000000..641640f0 --- /dev/null +++ b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs @@ -0,0 +1,89 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System.Threading; + +using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; + +namespace Ryujinx.Audio.Backends.Dummy +{ + public class DummyHardwareDeviceDriver : IHardwareDeviceDriver + { + private ManualResetEvent _updateRequiredEvent; + private ManualResetEvent _pauseEvent; + + public static bool IsSupported => true; + + public DummyHardwareDeviceDriver() + { + _updateRequiredEvent = new ManualResetEvent(false); + _pauseEvent = new ManualResetEvent(true); + } + + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume) + { + if (sampleRate == 0) + { + sampleRate = Constants.TargetSampleRate; + } + + if (channelCount == 0) + { + channelCount = 2; + } + + if (direction == Direction.Output) + { + return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount, volume); + } + else + { + return new DummyHardwareDeviceSessionInput(this, memoryManager, sampleFormat, sampleRate, channelCount); + } + } + + public ManualResetEvent GetUpdateRequiredEvent() + { + return _updateRequiredEvent; + } + + public ManualResetEvent GetPauseEvent() + { + return _pauseEvent; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // NOTE: The _updateRequiredEvent will be disposed somewhere else. + _pauseEvent.Dispose(); + } + } + + public bool SupportsSampleRate(uint sampleRate) + { + return true; + } + + public bool SupportsSampleFormat(SampleFormat sampleFormat) + { + return true; + } + + public bool SupportsDirection(Direction direction) + { + return direction == Direction.Output || direction == Direction.Input; + } + + public bool SupportsChannelCount(uint channelCount) + { + return channelCount == 1 || channelCount == 2 || channelCount == 6; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionInput.cs b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionInput.cs new file mode 100644 index 00000000..845713a1 --- /dev/null +++ b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionInput.cs @@ -0,0 +1,67 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System; + +namespace Ryujinx.Audio.Backends.Dummy +{ + class DummyHardwareDeviceSessionInput : IHardwareDeviceSession + { + private float _volume; + private IHardwareDeviceDriver _manager; + private IVirtualMemoryManager _memoryManager; + + public DummyHardwareDeviceSessionInput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) + { + _volume = 1.0f; + _manager = manager; + _memoryManager = memoryManager; + } + + public void Dispose() + { + // Nothing to do. + } + + public ulong GetPlayedSampleCount() + { + // Not implemented for input. + throw new NotSupportedException(); + } + + public float GetVolume() + { + return _volume; + } + + public void PrepareToClose() { } + + public void QueueBuffer(AudioBuffer buffer) + { + _memoryManager.Fill(buffer.DataPointer, buffer.DataSize, 0); + + _manager.GetUpdateRequiredEvent().Set(); + } + + public bool RegisterBuffer(AudioBuffer buffer) + { + return buffer.DataPointer != 0; + } + + public void SetVolume(float volume) + { + _volume = volume; + } + + public void Start() { } + + public void Stop() { } + + public void UnregisterBuffer(AudioBuffer buffer) { } + + public bool WasBufferFullyConsumed(AudioBuffer buffer) + { + return true; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs new file mode 100644 index 00000000..8e2c949e --- /dev/null +++ b/src/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs @@ -0,0 +1,62 @@ +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System.Threading; + +namespace Ryujinx.Audio.Backends.Dummy +{ + internal class DummyHardwareDeviceSessionOutput : HardwareDeviceSessionOutputBase + { + private float _volume; + private IHardwareDeviceDriver _manager; + + private ulong _playedSampleCount; + + public DummyHardwareDeviceSessionOutput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + { + _volume = requestedVolume; + _manager = manager; + } + + public override void Dispose() + { + // Nothing to do. + } + + public override ulong GetPlayedSampleCount() + { + return Interlocked.Read(ref _playedSampleCount); + } + + public override float GetVolume() + { + return _volume; + } + + public override void PrepareToClose() { } + + public override void QueueBuffer(AudioBuffer buffer) + { + Interlocked.Add(ref _playedSampleCount, GetSampleCount(buffer)); + + _manager.GetUpdateRequiredEvent().Set(); + } + + public override void SetVolume(float volume) + { + _volume = volume; + } + + public override void Start() { } + + public override void Stop() { } + + public override void UnregisterBuffer(AudioBuffer buffer) { } + + public override bool WasBufferFullyConsumed(AudioBuffer buffer) + { + return true; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Common/AudioBuffer.cs b/src/Ryujinx.Audio/Common/AudioBuffer.cs new file mode 100644 index 00000000..b79401b7 --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioBuffer.cs @@ -0,0 +1,37 @@ +using Ryujinx.Audio.Integration; + +namespace Ryujinx.Audio.Common +{ + /// + /// Represent an audio buffer that will be used by an . + /// + public class AudioBuffer + { + /// + /// Unique tag of this buffer. + /// + /// Unique per session + public ulong BufferTag; + + /// + /// Pointer to the user samples. + /// + public ulong DataPointer; + + /// + /// Size of the user samples region. + /// + public ulong DataSize; + + /// + /// The timestamp at which the buffer was played. + /// + /// Not used but useful for debugging + public ulong PlayedTimestamp; + + /// + /// The user samples. + /// + public byte[] Data; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Common/AudioDeviceSession.cs b/src/Ryujinx.Audio/Common/AudioDeviceSession.cs new file mode 100644 index 00000000..0191f7cc --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioDeviceSession.cs @@ -0,0 +1,518 @@ +using Ryujinx.Audio.Integration; +using Ryujinx.Common; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Common +{ + /// + /// An audio device session. + /// + class AudioDeviceSession : IDisposable + { + /// + /// The volume of the . + /// + private float _volume; + + /// + /// The state of the . + /// + private AudioDeviceState _state; + + /// + /// Array of all buffers currently used or released. + /// + private AudioBuffer[] _buffers; + + /// + /// The server index inside (appended but not queued to device driver). + /// + private uint _serverBufferIndex; + + /// + /// The hardware index inside (queued to device driver). + /// + private uint _hardwareBufferIndex; + + /// + /// The released index inside (released by the device driver). + /// + private uint _releasedBufferIndex; + + /// + /// The count of buffer appended (server side). + /// + private uint _bufferAppendedCount; + + /// + /// The count of buffer registered (driver side). + /// + private uint _bufferRegisteredCount; + + /// + /// The count of buffer released (released by the driver side). + /// + private uint _bufferReleasedCount; + + /// + /// The released buffer event. + /// + private IWritableEvent _bufferEvent; + + /// + /// The session on the device driver. + /// + private IHardwareDeviceSession _hardwareDeviceSession; + + /// + /// Max number of buffers that can be registered to the device driver at a time. + /// + private uint _bufferRegisteredLimit; + + /// + /// Create a new . + /// + /// The device driver session associated + /// The release buffer event + /// The max number of buffers that can be registered to the device driver at a time + public AudioDeviceSession(IHardwareDeviceSession deviceSession, IWritableEvent bufferEvent, uint bufferRegisteredLimit = 4) + { + _bufferEvent = bufferEvent; + _hardwareDeviceSession = deviceSession; + _bufferRegisteredLimit = bufferRegisteredLimit; + + _buffers = new AudioBuffer[Constants.AudioDeviceBufferCountMax]; + _serverBufferIndex = 0; + _hardwareBufferIndex = 0; + _releasedBufferIndex = 0; + + _bufferAppendedCount = 0; + _bufferRegisteredCount = 0; + _bufferReleasedCount = 0; + _volume = deviceSession.GetVolume(); + _state = AudioDeviceState.Stopped; + } + + /// + /// Get the released buffer event. + /// + /// The released buffer event + public IWritableEvent GetBufferEvent() + { + return _bufferEvent; + } + + /// + /// Get the state of the session. + /// + /// The state of the session + public AudioDeviceState GetState() + { + Debug.Assert(_state == AudioDeviceState.Started || _state == AudioDeviceState.Stopped); + + return _state; + } + + /// + /// Get the total buffer count (server + driver + released). + /// + /// Return the total buffer count + private uint GetTotalBufferCount() + { + uint bufferCount = _bufferAppendedCount + _bufferRegisteredCount + _bufferReleasedCount; + + Debug.Assert(bufferCount <= Constants.AudioDeviceBufferCountMax); + + return bufferCount; + } + + /// + /// Register a new on the server side. + /// + /// The to register + /// True if the operation succeeded + private bool RegisterBuffer(AudioBuffer buffer) + { + if (GetTotalBufferCount() == Constants.AudioDeviceBufferCountMax) + { + return false; + } + + _buffers[_serverBufferIndex] = buffer; + _serverBufferIndex = (_serverBufferIndex + 1) % Constants.AudioDeviceBufferCountMax; + _bufferAppendedCount++; + + return true; + } + + /// + /// Flush server buffers to hardware. + /// + private void FlushToHardware() + { + uint bufferToFlushCount = Math.Min(Math.Min(_bufferAppendedCount, 4), _bufferRegisteredLimit - _bufferRegisteredCount); + + AudioBuffer[] buffersToFlush = new AudioBuffer[bufferToFlushCount]; + + uint hardwareBufferIndex = _hardwareBufferIndex; + + for (int i = 0; i < buffersToFlush.Length; i++) + { + buffersToFlush[i] = _buffers[hardwareBufferIndex]; + + _bufferAppendedCount--; + _bufferRegisteredCount++; + + hardwareBufferIndex = (hardwareBufferIndex + 1) % Constants.AudioDeviceBufferCountMax; + } + + _hardwareBufferIndex = hardwareBufferIndex; + + for (int i = 0; i < buffersToFlush.Length; i++) + { + _hardwareDeviceSession.QueueBuffer(buffersToFlush[i]); + } + } + + /// + /// Get the current index of the playing on the driver side. + /// + /// The output index of the playing on the driver side + /// True if any buffer is playing + private bool TryGetPlayingBufferIndex(out uint playingIndex) + { + if (_bufferRegisteredCount > 0) + { + playingIndex = (_hardwareBufferIndex - _bufferRegisteredCount) % Constants.AudioDeviceBufferCountMax; + + return true; + } + + playingIndex = 0; + + return false; + } + + /// + /// Try to pop the playing on the driver side. + /// + /// The output playing on the driver side + /// True if any buffer is playing + private bool TryPopPlayingBuffer(out AudioBuffer buffer) + { + if (_bufferRegisteredCount > 0) + { + uint bufferIndex = (_hardwareBufferIndex - _bufferRegisteredCount) % Constants.AudioDeviceBufferCountMax; + + buffer = _buffers[bufferIndex]; + + _buffers[bufferIndex] = null; + + _bufferRegisteredCount--; + + return true; + } + + buffer = null; + + return false; + } + + /// + /// Try to pop a released by the driver side. + /// + /// The output released by the driver side + /// True if any buffer has been released + public bool TryPopReleasedBuffer(out AudioBuffer buffer) + { + if (_bufferReleasedCount > 0) + { + uint bufferIndex = (_releasedBufferIndex - _bufferReleasedCount) % Constants.AudioDeviceBufferCountMax; + + buffer = _buffers[bufferIndex]; + + _buffers[bufferIndex] = null; + + _bufferReleasedCount--; + + return true; + } + + buffer = null; + + return false; + } + + /// + /// Release a . + /// + /// The to release + private void ReleaseBuffer(AudioBuffer buffer) + { + buffer.PlayedTimestamp = (ulong)PerformanceCounter.ElapsedNanoseconds; + + _bufferRegisteredCount--; + _bufferReleasedCount++; + + _releasedBufferIndex = (_releasedBufferIndex + 1) % Constants.AudioDeviceBufferCountMax; + } + + /// + /// Update the released buffers. + /// + /// True if the session is currently stopping + private void UpdateReleaseBuffers(bool updateForStop = false) + { + bool wasAnyBuffersReleased = false; + + while (TryGetPlayingBufferIndex(out uint playingIndex)) + { + if (!updateForStop && !_hardwareDeviceSession.WasBufferFullyConsumed(_buffers[playingIndex])) + { + break; + } + + if (updateForStop) + { + _hardwareDeviceSession.UnregisterBuffer(_buffers[playingIndex]); + } + + ReleaseBuffer(_buffers[playingIndex]); + + wasAnyBuffersReleased = true; + } + + if (wasAnyBuffersReleased) + { + _bufferEvent.Signal(); + } + } + + /// + /// Append a new . + /// + /// The to append + /// True if the buffer was appended + public bool AppendBuffer(AudioBuffer buffer) + { + if (_hardwareDeviceSession.RegisterBuffer(buffer)) + { + if (RegisterBuffer(buffer)) + { + FlushToHardware(); + + return true; + } + + _hardwareDeviceSession.UnregisterBuffer(buffer); + } + + return false; + } + + public bool AppendUacBuffer(AudioBuffer buffer, uint handle) + { + // NOTE: On hardware, there is another RegisterBuffer method taking an handle. + // This variant of the call always return false (stubbed?) as a result this logic will never succeed. + + return false; + } + + /// + /// Start the audio session. + /// + /// A reporting an error or a success + public ResultCode Start() + { + if (_state == AudioDeviceState.Started) + { + return ResultCode.OperationFailed; + } + + _hardwareDeviceSession.Start(); + + _state = AudioDeviceState.Started; + + FlushToHardware(); + + _hardwareDeviceSession.SetVolume(_volume); + + return ResultCode.Success; + } + + /// + /// Stop the audio session. + /// + /// A reporting an error or a success + public ResultCode Stop() + { + if (_state == AudioDeviceState.Started) + { + _hardwareDeviceSession.Stop(); + + UpdateReleaseBuffers(true); + + _state = AudioDeviceState.Stopped; + } + + return ResultCode.Success; + } + + /// + /// Get the volume of the session. + /// + /// The volume of the session + public float GetVolume() + { + return _hardwareDeviceSession.GetVolume(); + } + + /// + /// Set the volume of the session. + /// + /// The new volume to set + public void SetVolume(float volume) + { + _volume = volume; + + if (_state == AudioDeviceState.Started) + { + _hardwareDeviceSession.SetVolume(volume); + } + } + + /// + /// Get the count of buffer currently in use (server + driver side). + /// + /// The count of buffer currently in use + public uint GetBufferCount() + { + return _bufferAppendedCount + _bufferRegisteredCount; + } + + /// + /// Check if a buffer is present. + /// + /// The unique tag of the buffer + /// Return true if a buffer is present + public bool ContainsBuffer(ulong bufferTag) + { + uint bufferIndex = (_releasedBufferIndex - _bufferReleasedCount) % Constants.AudioDeviceBufferCountMax; + + uint totalBufferCount = GetTotalBufferCount(); + + for (int i = 0; i < totalBufferCount; i++) + { + if (_buffers[bufferIndex].BufferTag == bufferTag) + { + return true; + } + + bufferIndex = (bufferIndex + 1) % Constants.AudioDeviceBufferCountMax; + } + + return false; + } + + /// + /// Get the count of sample played in this session. + /// + /// The count of sample played in this session + public ulong GetPlayedSampleCount() + { + if (_state == AudioDeviceState.Stopped) + { + return 0; + } + else + { + return _hardwareDeviceSession.GetPlayedSampleCount(); + } + } + + /// + /// Flush all buffers to the initial state. + /// + /// True if any buffer was flushed + public bool FlushBuffers() + { + if (_state == AudioDeviceState.Stopped) + { + return false; + } + + uint bufferCount = GetBufferCount(); + + while (TryPopReleasedBuffer(out AudioBuffer buffer)) + { + _hardwareDeviceSession.UnregisterBuffer(buffer); + } + + while (TryPopPlayingBuffer(out AudioBuffer buffer)) + { + _hardwareDeviceSession.UnregisterBuffer(buffer); + } + + if (_bufferRegisteredCount == 0 || (_bufferReleasedCount + _bufferAppendedCount) > Constants.AudioDeviceBufferCountMax) + { + return false; + } + + _bufferReleasedCount += _bufferAppendedCount; + _releasedBufferIndex = (_releasedBufferIndex + _bufferAppendedCount) % Constants.AudioDeviceBufferCountMax; + _bufferAppendedCount = 0; + _hardwareBufferIndex = _serverBufferIndex; + + if (bufferCount > 0) + { + _bufferEvent.Signal(); + } + + return true; + } + + /// + /// Update the session. + /// + public void Update() + { + if (_state == AudioDeviceState.Started) + { + UpdateReleaseBuffers(); + FlushToHardware(); + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Tell the hardware session that we are ending. + _hardwareDeviceSession.PrepareToClose(); + + // Unregister all buffers + + while (TryPopReleasedBuffer(out AudioBuffer buffer)) + { + _hardwareDeviceSession.UnregisterBuffer(buffer); + } + + while (TryPopPlayingBuffer(out AudioBuffer buffer)) + { + _hardwareDeviceSession.UnregisterBuffer(buffer); + } + + // Finally dispose hardware session. + _hardwareDeviceSession.Dispose(); + + _bufferEvent.Signal(); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Common/AudioDeviceState.cs b/src/Ryujinx.Audio/Common/AudioDeviceState.cs new file mode 100644 index 00000000..b3f968da --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioDeviceState.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Audio.Common +{ + /// + /// Audio device state. + /// + public enum AudioDeviceState : uint + { + /// + /// The audio device is started. + /// + Started, + + /// + /// The audio device is stopped. + /// + Stopped + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Common/AudioInputConfiguration.cs b/src/Ryujinx.Audio/Common/AudioInputConfiguration.cs new file mode 100644 index 00000000..d3cfdd47 --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioInputConfiguration.cs @@ -0,0 +1,29 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Common +{ + /// + /// Audio user input configuration. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AudioInputConfiguration + { + /// + /// The target sample rate of the user. + /// + /// Only 48000Hz is considered valid, other sample rates will be refused. + public uint SampleRate; + + /// + /// The target channel count of the user. + /// + /// Only Stereo and Surround are considered valid, other configurations will be refused. + /// Not used in audin. + public ushort ChannelCount; + + /// + /// Reserved/unused. + /// + private ushort _reserved; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Common/AudioOutputConfiguration.cs b/src/Ryujinx.Audio/Common/AudioOutputConfiguration.cs new file mode 100644 index 00000000..e17e1757 --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioOutputConfiguration.cs @@ -0,0 +1,37 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Common +{ + /// + /// Audio system output configuration. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AudioOutputConfiguration + { + /// + /// The target sample rate of the system. + /// + public uint SampleRate; + + /// + /// The target channel count of the system. + /// + public uint ChannelCount; + + /// + /// Reserved/unused + /// + public SampleFormat SampleFormat; + + /// + /// Reserved/unused. + /// + private Array3 _padding; + + /// + /// The initial audio system state. + /// + public AudioDeviceState AudioOutState; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Common/AudioUserBuffer.cs b/src/Ryujinx.Audio/Common/AudioUserBuffer.cs new file mode 100644 index 00000000..50ab67fa --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioUserBuffer.cs @@ -0,0 +1,36 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Common +{ + /// + /// Audio user buffer. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AudioUserBuffer + { + /// + /// Pointer to the next buffer (ignored). + /// + public ulong NextBuffer; + + /// + /// Pointer to the user samples. + /// + public ulong Data; + + /// + /// Capacity of the buffer (unused). + /// + public ulong Capacity; + + /// + /// Size of the user samples region. + /// + public ulong DataSize; + + /// + /// Offset in the user samples region (unused). + /// + public ulong DataOffset; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Common/SampleFormat.cs b/src/Ryujinx.Audio/Common/SampleFormat.cs new file mode 100644 index 00000000..901410a2 --- /dev/null +++ b/src/Ryujinx.Audio/Common/SampleFormat.cs @@ -0,0 +1,43 @@ +namespace Ryujinx.Audio.Common +{ + /// + /// Sample format definition. + /// + public enum SampleFormat : byte + { + /// + /// Invalid sample format. + /// + Invalid = 0, + + /// + /// PCM8 sample format. (unsupported) + /// + PcmInt8 = 1, + + /// + /// PCM16 sample format. + /// + PcmInt16 = 2, + + /// + /// PCM24 sample format. (unsupported) + /// + PcmInt24 = 3, + + /// + /// PCM32 sample format. + /// + PcmInt32 = 4, + + /// + /// PCM Float sample format. + /// + PcmFloat = 5, + + /// + /// ADPCM sample format. (Also known as GC-ADPCM) + /// + Adpcm = 6 + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Constants.cs b/src/Ryujinx.Audio/Constants.cs new file mode 100644 index 00000000..7d2ffa57 --- /dev/null +++ b/src/Ryujinx.Audio/Constants.cs @@ -0,0 +1,175 @@ +namespace Ryujinx.Audio +{ + /// + /// Define constants used by the audio system. + /// + public static class Constants + { + /// + /// The default device output name. + /// + public const string DefaultDeviceOutputName = "DeviceOut"; + + /// + /// The default device input name. + /// + public const string DefaultDeviceInputName = "BuiltInHeadset"; + + /// + /// The maximum number of channels supported. (6 channels for 5.1 surround) + /// + public const int ChannelCountMax = 6; + + /// + /// The maximum number of channels supported per voice. + /// + public const int VoiceChannelCountMax = ChannelCountMax; + + /// + /// The maximum count of mix buffer supported per operations (volumes, mix effect, ...) + /// + public const int MixBufferCountMax = 24; + + /// + /// The maximum count of wavebuffer per voice. + /// + public const int VoiceWaveBufferCount = 4; + + /// + /// The maximum count of biquad filter per voice. + /// + public const int VoiceBiquadFilterCount = 2; + + /// + /// The lowest priority that a voice can have. + /// + public const int VoiceLowestPriority = 0xFF; + + /// + /// The highest priority that a voice can have. + /// + /// Voices with the highest priority will not be dropped if a voice drop needs to occur. + public const int VoiceHighestPriority = 0; + + /// + /// Maximum that can be returned by . + /// + public const int MaxErrorInfos = 10; + + /// + /// Default alignment for buffers. + /// + public const int BufferAlignment = 0x40; + + /// + /// Alignment required for the work buffer. + /// + public const int WorkBufferAlignment = 0x1000; + + /// + /// Alignment required for every performance metrics frame. + /// + public const int PerformanceMetricsPerFramesSizeAlignment = 0x100; + + /// + /// The id of the final mix. + /// + public const int FinalMixId = 0; + + /// + /// The id defining an unused mix id. + /// + public const int UnusedMixId = int.MaxValue; + + /// + /// The id defining an unused splitter id as a signed integer. + /// + public const int UnusedSplitterIdInt = -1; + + /// + /// The id defining an unused splitter id. + /// + public const uint UnusedSplitterId = uint.MaxValue; + + /// + /// The id of invalid/unused node id. + /// + public const int InvalidNodeId = -268435456; + + /// + /// The indice considered invalid for processing order. + /// + public const int InvalidProcessingOrder = -1; + + /// + /// The maximum number of audio renderer sessions allowed to be created system wide. + /// + public const int AudioRendererSessionCountMax = 2; + + /// + /// The maximum number of audio output sessions allowed to be created system wide. + /// + public const int AudioOutSessionCountMax = 12; + + /// + /// The maximum number of audio input sessions allowed to be created system wide. + /// + public const int AudioInSessionCountMax = 4; + + /// + /// Maximum buffers supported by one audio device session. + /// + public const int AudioDeviceBufferCountMax = 32; + + /// + /// The target sample rate of the audio renderer. (48kHz) + /// + public const uint TargetSampleRate = 48000; + + /// + /// The target sample size of the audio renderer. (PCM16) + /// + public const int TargetSampleSize = sizeof(ushort); + + /// + /// The target sample count per audio renderer update. + /// + public const int TargetSampleCount = 240; + + /// + /// The size of an upsampler entry to process upsampling to . + /// + public const int UpSampleEntrySize = TargetSampleCount * VoiceChannelCountMax; + + /// + /// The target audio latency computed from and . + /// + public const int AudioProcessorMaxUpdateTimeTarget = 1000000000 / ((int)TargetSampleRate / TargetSampleCount); // 5.00 ms + + /// + /// The maximum update time of the DSP on original hardware. + /// + public const int AudioProcessorMaxUpdateTime = 5760000; // 5.76 ms + + /// + /// The maximum update time per audio renderer session. + /// + public const int AudioProcessorMaxUpdateTimePerSessions = AudioProcessorMaxUpdateTime / AudioRendererSessionCountMax; + + /// + /// Guest timer frequency used for system ticks. + /// + public const int TargetTimerFrequency = 19200000; + + /// + /// The default coefficients used for standard 5.1 surround to stereo downmixing. + /// + public static float[] DefaultSurroundToStereoCoefficients = new float[4] + { + 1.0f, + 0.707f, + 0.251f, + 0.707f, + }; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Input/AudioInputManager.cs b/src/Ryujinx.Audio/Input/AudioInputManager.cs new file mode 100644 index 00000000..ac012c4a --- /dev/null +++ b/src/Ryujinx.Audio/Input/AudioInputManager.cs @@ -0,0 +1,266 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; + +namespace Ryujinx.Audio.Input +{ + /// + /// The audio input manager. + /// + public class AudioInputManager : IDisposable + { + private object _lock = new object(); + + /// + /// Lock used for session allocation. + /// + private object _sessionLock = new object(); + + /// + /// The session ids allocation table. + /// + private int[] _sessionIds; + + /// + /// The device driver. + /// + private IHardwareDeviceDriver _deviceDriver; + + /// + /// The events linked to each session. + /// + private IWritableEvent[] _sessionsBufferEvents; + + /// + /// The session instances. + /// + private AudioInputSystem[] _sessions; + + /// + /// The count of active sessions. + /// + private int _activeSessionCount; + + /// + /// The dispose state. + /// + private int _disposeState; + + /// + /// Create a new . + /// + public AudioInputManager() + { + _sessionIds = new int[Constants.AudioInSessionCountMax]; + _sessions = new AudioInputSystem[Constants.AudioInSessionCountMax]; + _activeSessionCount = 0; + + for (int i = 0; i < _sessionIds.Length; i++) + { + _sessionIds[i] = i; + } + } + + /// + /// Initialize the . + /// + /// The device driver. + /// The events associated to each session. + public void Initialize(IHardwareDeviceDriver deviceDriver, IWritableEvent[] sessionRegisterEvents) + { + _deviceDriver = deviceDriver; + _sessionsBufferEvents = sessionRegisterEvents; + } + + /// + /// Acquire a new session id. + /// + /// A new session id. + private int AcquireSessionId() + { + lock (_sessionLock) + { + int index = _activeSessionCount; + + Debug.Assert(index < _sessionIds.Length); + + int sessionId = _sessionIds[index]; + + _sessionIds[index] = -1; + + _activeSessionCount++; + + Logger.Info?.Print(LogClass.AudioRenderer, $"Registered new input ({sessionId})"); + + return sessionId; + } + } + + /// + /// Release a given . + /// + /// The session id to release. + private void ReleaseSessionId(int sessionId) + { + lock (_sessionLock) + { + Debug.Assert(_activeSessionCount > 0); + + int newIndex = --_activeSessionCount; + + _sessionIds[newIndex] = sessionId; + } + + Logger.Info?.Print(LogClass.AudioRenderer, $"Unregistered input ({sessionId})"); + } + + /// + /// Used to update audio input system. + /// + public void Update() + { + lock (_sessionLock) + { + foreach (AudioInputSystem input in _sessions) + { + input?.Update(); + } + } + } + + /// + /// Register a new . + /// + /// The to register. + private void Register(AudioInputSystem input) + { + lock (_sessionLock) + { + _sessions[input.GetSessionId()] = input; + } + } + + /// + /// Unregister a new . + /// + /// The to unregister. + internal void Unregister(AudioInputSystem input) + { + lock (_sessionLock) + { + int sessionId = input.GetSessionId(); + + _sessions[input.GetSessionId()] = null; + + ReleaseSessionId(sessionId); + } + } + + /// + /// Get the list of all audio inputs names. + /// + /// If true, filter disconnected devices + /// The list of all audio inputs name + public string[] ListAudioIns(bool filtered) + { + if (filtered) + { + // TODO: Detect if the driver supports audio input + } + + return new string[] { Constants.DefaultDeviceInputName }; + } + + /// + /// Open a new . + /// + /// The output device name selected by the + /// The output audio configuration selected by the + /// The new + /// The memory manager that will be used for all guest memory operations + /// The input device name wanted by the user + /// The sample format to use + /// The user configuration + /// The applet resource user id of the application + /// The process handle of the application + /// A reporting an error or a success + public ResultCode OpenAudioIn(out string outputDeviceName, + out AudioOutputConfiguration outputConfiguration, + out AudioInputSystem obj, + IVirtualMemoryManager memoryManager, + string inputDeviceName, + SampleFormat sampleFormat, + ref AudioInputConfiguration parameter, + ulong appletResourceUserId, + uint processHandle) + { + int sessionId = AcquireSessionId(); + + _sessionsBufferEvents[sessionId].Clear(); + + IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Input, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount); + + AudioInputSystem audioIn = new AudioInputSystem(this, _lock, deviceSession, _sessionsBufferEvents[sessionId]); + + ResultCode result = audioIn.Initialize(inputDeviceName, sampleFormat, ref parameter, sessionId); + + if (result == ResultCode.Success) + { + outputDeviceName = audioIn.DeviceName; + outputConfiguration = new AudioOutputConfiguration + { + ChannelCount = audioIn.ChannelCount, + SampleFormat = audioIn.SampleFormat, + SampleRate = audioIn.SampleRate, + AudioOutState = audioIn.GetState(), + }; + + obj = audioIn; + + Register(audioIn); + } + else + { + ReleaseSessionId(sessionId); + + obj = null; + outputDeviceName = null; + outputConfiguration = default; + } + + return result; + } + + public void Dispose() + { + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Clone the sessions array to dispose them outside the lock. + AudioInputSystem[] sessions; + + lock (_sessionLock) + { + sessions = _sessions.ToArray(); + } + + foreach (AudioInputSystem input in sessions) + { + input?.Dispose(); + } + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Input/AudioInputSystem.cs b/src/Ryujinx.Audio/Input/AudioInputSystem.cs new file mode 100644 index 00000000..b3ca0fd6 --- /dev/null +++ b/src/Ryujinx.Audio/Input/AudioInputSystem.cs @@ -0,0 +1,392 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using System; +using System.Threading; + +namespace Ryujinx.Audio.Input +{ + /// + /// Audio input system. + /// + public class AudioInputSystem : IDisposable + { + /// + /// The session id associated to the . + /// + private int _sessionId; + + /// + /// The session the . + /// + private AudioDeviceSession _session; + + /// + /// The target device name of the . + /// + public string DeviceName { get; private set; } + + /// + /// The target sample rate of the . + /// + public uint SampleRate { get; private set; } + + /// + /// The target channel count of the . + /// + public uint ChannelCount { get; private set; } + + /// + /// The target sample format of the . + /// + public SampleFormat SampleFormat { get; private set; } + + /// + /// The owning this. + /// + private AudioInputManager _manager; + + /// + /// The lock of the parent. + /// + private object _parentLock; + + /// + /// The dispose state. + /// + private int _disposeState; + + /// + /// Create a new . + /// + /// The manager instance + /// The lock of the manager + /// The hardware device session + /// The buffer release event of the audio input + public AudioInputSystem(AudioInputManager manager, object parentLock, IHardwareDeviceSession deviceSession, IWritableEvent bufferEvent) + { + _manager = manager; + _parentLock = parentLock; + _session = new AudioDeviceSession(deviceSession, bufferEvent); + } + + /// + /// Get the default device name on the system. + /// + /// The default device name on the system. + private static string GetDeviceDefaultName() + { + return Constants.DefaultDeviceInputName; + } + + /// + /// Check if a given configuration and device name is valid on the system. + /// + /// The configuration to check. + /// The device name to check. + /// A reporting an error or a success. + private static ResultCode IsConfigurationValid(ref AudioInputConfiguration configuration, string deviceName) + { + if (deviceName.Length != 0 && !deviceName.Equals(GetDeviceDefaultName())) + { + return ResultCode.DeviceNotFound; + } + else if (configuration.SampleRate != 0 && configuration.SampleRate != Constants.TargetSampleRate) + { + return ResultCode.UnsupportedSampleRate; + } + else if (configuration.ChannelCount != 0 && configuration.ChannelCount != 1 && configuration.ChannelCount != 2 && configuration.ChannelCount != 6) + { + return ResultCode.UnsupportedChannelConfiguration; + } + + return ResultCode.Success; + } + + /// + /// Get the released buffer event. + /// + /// The released buffer event + public IWritableEvent RegisterBufferEvent() + { + lock (_parentLock) + { + return _session.GetBufferEvent(); + } + } + + /// + /// Update the . + /// + public void Update() + { + lock (_parentLock) + { + _session.Update(); + } + } + + /// + /// Get the id of this session. + /// + /// The id of this session + public int GetSessionId() + { + return _sessionId; + } + + /// + /// Initialize the . + /// + /// The input device name wanted by the user + /// The sample format to use + /// The user configuration + /// The session id associated to this + /// A reporting an error or a success. + public ResultCode Initialize(string inputDeviceName, SampleFormat sampleFormat, ref AudioInputConfiguration parameter, int sessionId) + { + _sessionId = sessionId; + + ResultCode result = IsConfigurationValid(ref parameter, inputDeviceName); + + if (result == ResultCode.Success) + { + if (inputDeviceName.Length == 0) + { + DeviceName = GetDeviceDefaultName(); + } + else + { + DeviceName = inputDeviceName; + } + + if (parameter.ChannelCount == 6) + { + ChannelCount = 6; + } + else + { + ChannelCount = 2; + } + + SampleFormat = sampleFormat; + SampleRate = Constants.TargetSampleRate; + } + + return result; + } + + /// + /// Append a new audio buffer to the audio input. + /// + /// The unique tag of this buffer. + /// The buffer informations. + /// A reporting an error or a success. + public ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer userBuffer) + { + lock (_parentLock) + { + AudioBuffer buffer = new AudioBuffer + { + BufferTag = bufferTag, + DataPointer = userBuffer.Data, + DataSize = userBuffer.DataSize + }; + + if (_session.AppendBuffer(buffer)) + { + return ResultCode.Success; + } + + return ResultCode.BufferRingFull; + } + } + + /// + /// Append a new audio buffer to the audio input. + /// + /// This is broken by design, only added for completness. + /// The unique tag of this buffer. + /// The buffer informations. + /// Some unknown handle. + /// A reporting an error or a success. + public ResultCode AppendUacBuffer(ulong bufferTag, ref AudioUserBuffer userBuffer, uint handle) + { + lock (_parentLock) + { + AudioBuffer buffer = new AudioBuffer + { + BufferTag = bufferTag, + DataPointer = userBuffer.Data, + DataSize = userBuffer.DataSize + }; + + if (_session.AppendUacBuffer(buffer, handle)) + { + return ResultCode.Success; + } + + return ResultCode.BufferRingFull; + } + } + + /// + /// Get the release buffers. + /// + /// The buffer to write the release buffers + /// The count of released buffers + /// A reporting an error or a success. + public ResultCode GetReleasedBuffers(Span releasedBuffers, out uint releasedCount) + { + releasedCount = 0; + + // Ensure that the first entry is set to zero if no entries are returned. + if (releasedBuffers.Length > 0) + { + releasedBuffers[0] = 0; + } + + lock (_parentLock) + { + for (int i = 0; i < releasedBuffers.Length; i++) + { + if (!_session.TryPopReleasedBuffer(out AudioBuffer buffer)) + { + break; + } + + releasedBuffers[i] = buffer.BufferTag; + releasedCount++; + } + } + + return ResultCode.Success; + } + + /// + /// Get the current state of the . + /// + /// Return the curent sta\te of the + public AudioDeviceState GetState() + { + lock (_parentLock) + { + return _session.GetState(); + } + } + + /// + /// Start the audio session. + /// + /// A reporting an error or a success + public ResultCode Start() + { + lock (_parentLock) + { + return _session.Start(); + } + } + + /// + /// Stop the audio session. + /// + /// A reporting an error or a success + public ResultCode Stop() + { + lock (_parentLock) + { + return _session.Stop(); + } + } + + /// + /// Get the volume of the session. + /// + /// The volume of the session + public float GetVolume() + { + lock (_parentLock) + { + return _session.GetVolume(); + } + } + + /// + /// Set the volume of the session. + /// + /// The new volume to set + public void SetVolume(float volume) + { + lock (_parentLock) + { + _session.SetVolume(volume); + } + } + + /// + /// Get the count of buffer currently in use (server + driver side). + /// + /// The count of buffer currently in use + public uint GetBufferCount() + { + lock (_parentLock) + { + return _session.GetBufferCount(); + } + } + + /// + /// Check if a buffer is present. + /// + /// The unique tag of the buffer + /// Return true if a buffer is present + public bool ContainsBuffer(ulong bufferTag) + { + lock (_parentLock) + { + return _session.ContainsBuffer(bufferTag); + } + } + + /// + /// Get the count of sample played in this session. + /// + /// The count of sample played in this session + public ulong GetPlayedSampleCount() + { + lock (_parentLock) + { + return _session.GetPlayedSampleCount(); + } + } + + /// + /// Flush all buffers to the initial state. + /// + /// True if any buffers was flushed + public bool FlushBuffers() + { + lock (_parentLock) + { + return _session.FlushBuffers(); + } + } + + public void Dispose() + { + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _session.Dispose(); + + _manager.Unregister(this); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs b/src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs new file mode 100644 index 00000000..552f1ab2 --- /dev/null +++ b/src/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs @@ -0,0 +1,75 @@ +using Ryujinx.Audio.Common; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Integration +{ + public class HardwareDeviceImpl : IHardwareDevice + { + private IHardwareDeviceSession _session; + private uint _channelCount; + private uint _sampleRate; + private uint _currentBufferTag; + + private byte[] _buffer; + + public HardwareDeviceImpl(IHardwareDeviceDriver deviceDriver, uint channelCount, uint sampleRate, float volume) + { + _session = deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, null, SampleFormat.PcmInt16, sampleRate, channelCount, volume); + _channelCount = channelCount; + _sampleRate = sampleRate; + _currentBufferTag = 0; + + _buffer = new byte[Constants.TargetSampleCount * channelCount * sizeof(ushort)]; + + _session.Start(); + } + + public void AppendBuffer(ReadOnlySpan data, uint channelCount) + { + data.CopyTo(MemoryMarshal.Cast(_buffer)); + + _session.QueueBuffer(new AudioBuffer + { + DataPointer = _currentBufferTag++, + Data = _buffer, + DataSize = (ulong)_buffer.Length, + }); + + _currentBufferTag = _currentBufferTag % 4; + } + + public void SetVolume(float volume) + { + _session.SetVolume(volume); + } + + public float GetVolume() + { + return _session.GetVolume(); + } + + public uint GetChannelCount() + { + return _channelCount; + } + + public uint GetSampleRate() + { + return _sampleRate; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _session.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Integration/IHardwareDevice.cs b/src/Ryujinx.Audio/Integration/IHardwareDevice.cs new file mode 100644 index 00000000..300de8c5 --- /dev/null +++ b/src/Ryujinx.Audio/Integration/IHardwareDevice.cs @@ -0,0 +1,55 @@ +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Integration +{ + /// + /// Represent an hardware device used in + /// + public interface IHardwareDevice : IDisposable + { + /// + /// Sets the volume level for this device. + /// + /// The volume level to set. + void SetVolume(float volume); + + /// + /// Gets the volume level for this device. + /// + /// The volume level of this device. + float GetVolume(); + + /// + /// Get the supported sample rate of this device. + /// + /// The supported sample rate of this device. + uint GetSampleRate(); + + /// + /// Get the channel count supported by this device. + /// + /// The channel count supported by this device. + uint GetChannelCount(); + + /// + /// Appends new PCM16 samples to the device. + /// + /// The new PCM16 samples. + /// The number of channels. + void AppendBuffer(ReadOnlySpan data, uint channelCount); + + /// + /// Check if the audio renderer needs to perform downmixing. + /// + /// True if downmixing is needed. + public bool NeedDownmixing() + { + uint channelCount = GetChannelCount(); + + Debug.Assert(channelCount > 0 && channelCount <= Constants.ChannelCountMax); + + return channelCount != Constants.ChannelCountMax; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs b/src/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs new file mode 100644 index 00000000..4ed17951 --- /dev/null +++ b/src/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs @@ -0,0 +1,36 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Memory; +using System; +using System.Threading; + +namespace Ryujinx.Audio.Integration +{ + /// + /// Represent an hardware device driver used in . + /// + public interface IHardwareDeviceDriver : IDisposable + { + public enum Direction + { + Input, + Output + } + + IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume = 1f); + + ManualResetEvent GetUpdateRequiredEvent(); + ManualResetEvent GetPauseEvent(); + + bool SupportsDirection(Direction direction); + bool SupportsSampleRate(uint sampleRate); + bool SupportsSampleFormat(SampleFormat sampleFormat); + bool SupportsChannelCount(uint channelCount); + + static abstract bool IsSupported { get; } + + IHardwareDeviceDriver GetRealDeviceDriver() + { + return this; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Integration/IHardwareDeviceSession.cs b/src/Ryujinx.Audio/Integration/IHardwareDeviceSession.cs new file mode 100644 index 00000000..400daec0 --- /dev/null +++ b/src/Ryujinx.Audio/Integration/IHardwareDeviceSession.cs @@ -0,0 +1,28 @@ +using Ryujinx.Audio.Common; +using System; + +namespace Ryujinx.Audio.Integration +{ + public interface IHardwareDeviceSession : IDisposable + { + bool RegisterBuffer(AudioBuffer buffer); + + void UnregisterBuffer(AudioBuffer buffer); + + void QueueBuffer(AudioBuffer buffer); + + bool WasBufferFullyConsumed(AudioBuffer buffer); + + void SetVolume(float volume); + + float GetVolume(); + + ulong GetPlayedSampleCount(); + + void Start(); + + void Stop(); + + void PrepareToClose(); + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Integration/IWritableEvent.cs b/src/Ryujinx.Audio/Integration/IWritableEvent.cs new file mode 100644 index 00000000..9a12e3d2 --- /dev/null +++ b/src/Ryujinx.Audio/Integration/IWritableEvent.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Audio.Integration +{ + /// + /// Represent a writable event with manual clear. + /// + public interface IWritableEvent + { + /// + /// Signal the event. + /// + void Signal(); + + /// + /// Clear the signaled state of the event. + /// + void Clear(); + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Output/AudioOutputManager.cs b/src/Ryujinx.Audio/Output/AudioOutputManager.cs new file mode 100644 index 00000000..8c21f76a --- /dev/null +++ b/src/Ryujinx.Audio/Output/AudioOutputManager.cs @@ -0,0 +1,296 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; + +namespace Ryujinx.Audio.Output +{ + /// + /// The audio output manager. + /// + public class AudioOutputManager : IDisposable + { + private object _lock = new object(); + + /// + /// Lock used for session allocation. + /// + private object _sessionLock = new object(); + + /// + /// The session ids allocation table. + /// + private int[] _sessionIds; + + /// + /// The device driver. + /// + private IHardwareDeviceDriver _deviceDriver; + + /// + /// The events linked to each session. + /// + private IWritableEvent[] _sessionsBufferEvents; + + /// + /// The session instances. + /// + private AudioOutputSystem[] _sessions; + + /// + /// The count of active sessions. + /// + private int _activeSessionCount; + + /// + /// The dispose state. + /// + private int _disposeState; + + /// + /// Create a new . + /// + public AudioOutputManager() + { + _sessionIds = new int[Constants.AudioOutSessionCountMax]; + _sessions = new AudioOutputSystem[Constants.AudioOutSessionCountMax]; + _activeSessionCount = 0; + + for (int i = 0; i < _sessionIds.Length; i++) + { + _sessionIds[i] = i; + } + } + + /// + /// Initialize the . + /// + /// The device driver. + /// The events associated to each session. + public void Initialize(IHardwareDeviceDriver deviceDriver, IWritableEvent[] sessionRegisterEvents) + { + _deviceDriver = deviceDriver; + _sessionsBufferEvents = sessionRegisterEvents; + } + + /// + /// Acquire a new session id. + /// + /// A new session id. + private int AcquireSessionId() + { + lock (_sessionLock) + { + int index = _activeSessionCount; + + Debug.Assert(index < _sessionIds.Length); + + int sessionId = _sessionIds[index]; + + _sessionIds[index] = -1; + + _activeSessionCount++; + + Logger.Info?.Print(LogClass.AudioRenderer, $"Registered new output ({sessionId})"); + + return sessionId; + } + } + + /// + /// Release a given . + /// + /// The session id to release. + private void ReleaseSessionId(int sessionId) + { + lock (_sessionLock) + { + Debug.Assert(_activeSessionCount > 0); + + int newIndex = --_activeSessionCount; + + _sessionIds[newIndex] = sessionId; + } + + Logger.Info?.Print(LogClass.AudioRenderer, $"Unregistered output ({sessionId})"); + } + + /// + /// Used to update audio output system. + /// + public void Update() + { + lock (_sessionLock) + { + foreach (AudioOutputSystem output in _sessions) + { + output?.Update(); + } + } + } + + /// + /// Register a new . + /// + /// The to register. + private void Register(AudioOutputSystem output) + { + lock (_sessionLock) + { + _sessions[output.GetSessionId()] = output; + } + } + + /// + /// Unregister a new . + /// + /// The to unregister. + internal void Unregister(AudioOutputSystem output) + { + lock (_sessionLock) + { + int sessionId = output.GetSessionId(); + + _sessions[output.GetSessionId()] = null; + + ReleaseSessionId(sessionId); + } + } + + /// + /// Get the list of all audio outputs name. + /// + /// The list of all audio outputs name + public string[] ListAudioOuts() + { + return new string[] { Constants.DefaultDeviceOutputName }; + } + + /// + /// Open a new . + /// + /// The output device name selected by the + /// The output audio configuration selected by the + /// The new + /// The memory manager that will be used for all guest memory operations + /// The input device name wanted by the user + /// The sample format to use + /// The user configuration + /// The applet resource user id of the application + /// The process handle of the application + /// A reporting an error or a success + public ResultCode OpenAudioOut(out string outputDeviceName, + out AudioOutputConfiguration outputConfiguration, + out AudioOutputSystem obj, + IVirtualMemoryManager memoryManager, + string inputDeviceName, + SampleFormat sampleFormat, + ref AudioInputConfiguration parameter, + ulong appletResourceUserId, + uint processHandle, + float volume) + { + int sessionId = AcquireSessionId(); + + _sessionsBufferEvents[sessionId].Clear(); + + IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount, volume); + + AudioOutputSystem audioOut = new AudioOutputSystem(this, _lock, deviceSession, _sessionsBufferEvents[sessionId]); + + ResultCode result = audioOut.Initialize(inputDeviceName, sampleFormat, ref parameter, sessionId); + + if (result == ResultCode.Success) + { + outputDeviceName = audioOut.DeviceName; + outputConfiguration = new AudioOutputConfiguration + { + ChannelCount = audioOut.ChannelCount, + SampleFormat = audioOut.SampleFormat, + SampleRate = audioOut.SampleRate, + AudioOutState = audioOut.GetState(), + }; + + obj = audioOut; + + Register(audioOut); + } + else + { + ReleaseSessionId(sessionId); + + obj = null; + outputDeviceName = null; + outputConfiguration = default; + } + + return result; + } + + /// + /// Sets the volume for all output devices. + /// + /// The volume to set. + public void SetVolume(float volume) + { + if (_sessions != null) + { + foreach (AudioOutputSystem session in _sessions) + { + session?.SetVolume(volume); + } + } + } + + /// + /// Gets the volume for all output devices. + /// + /// A float indicating the volume level. + public float GetVolume() + { + if (_sessions != null) + { + foreach (AudioOutputSystem session in _sessions) + { + if (session != null) + { + return session.GetVolume(); + } + } + } + + return 0.0f; + } + + public void Dispose() + { + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Clone the sessions array to dispose them outside the lock. + AudioOutputSystem[] sessions; + + lock (_sessionLock) + { + sessions = _sessions.ToArray(); + } + + foreach (AudioOutputSystem output in sessions) + { + output?.Dispose(); + } + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Output/AudioOutputSystem.cs b/src/Ryujinx.Audio/Output/AudioOutputSystem.cs new file mode 100644 index 00000000..93df87aa --- /dev/null +++ b/src/Ryujinx.Audio/Output/AudioOutputSystem.cs @@ -0,0 +1,365 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using System; +using System.Threading; + +namespace Ryujinx.Audio.Output +{ + /// + /// Audio output system. + /// + public class AudioOutputSystem : IDisposable + { + /// + /// The session id associated to the . + /// + private int _sessionId; + + /// + /// The session the . + /// + private AudioDeviceSession _session; + + /// + /// The target device name of the . + /// + public string DeviceName { get; private set; } + + /// + /// The target sample rate of the . + /// + public uint SampleRate { get; private set; } + + /// + /// The target channel count of the . + /// + public uint ChannelCount { get; private set; } + + /// + /// The target sample format of the . + /// + public SampleFormat SampleFormat { get; private set; } + + /// + /// The owning this. + /// + private AudioOutputManager _manager; + + /// + /// THe lock of the parent. + /// + private object _parentLock; + + /// + /// The dispose state. + /// + private int _disposeState; + + /// + /// Create a new . + /// + /// The manager instance + /// The lock of the manager + /// The hardware device session + /// The buffer release event of the audio output + public AudioOutputSystem(AudioOutputManager manager, object parentLock, IHardwareDeviceSession deviceSession, IWritableEvent bufferEvent) + { + _manager = manager; + _parentLock = parentLock; + _session = new AudioDeviceSession(deviceSession, bufferEvent); + } + + /// + /// Get the default device name on the system. + /// + /// The default device name on the system. + private static string GetDeviceDefaultName() + { + return Constants.DefaultDeviceOutputName; + } + + /// + /// Check if a given configuration and device name is valid on the system. + /// + /// The configuration to check. + /// The device name to check. + /// A reporting an error or a success. + private static ResultCode IsConfigurationValid(ref AudioInputConfiguration configuration, string deviceName) + { + if (deviceName.Length != 0 && !deviceName.Equals(GetDeviceDefaultName())) + { + return ResultCode.DeviceNotFound; + } + else if (configuration.SampleRate != 0 && configuration.SampleRate != Constants.TargetSampleRate) + { + return ResultCode.UnsupportedSampleRate; + } + else if (configuration.ChannelCount != 0 && configuration.ChannelCount != 1 && configuration.ChannelCount != 2 && configuration.ChannelCount != 6) + { + return ResultCode.UnsupportedChannelConfiguration; + } + + return ResultCode.Success; + } + + /// + /// Get the released buffer event. + /// + /// The released buffer event + public IWritableEvent RegisterBufferEvent() + { + lock (_parentLock) + { + return _session.GetBufferEvent(); + } + } + + /// + /// Update the . + /// + public void Update() + { + lock (_parentLock) + { + _session.Update(); + } + } + + /// + /// Get the id of this session. + /// + /// The id of this session + public int GetSessionId() + { + return _sessionId; + } + + /// + /// Initialize the . + /// + /// The input device name wanted by the user + /// The sample format to use + /// The user configuration + /// The session id associated to this + /// A reporting an error or a success. + public ResultCode Initialize(string inputDeviceName, SampleFormat sampleFormat, ref AudioInputConfiguration parameter, int sessionId) + { + _sessionId = sessionId; + + ResultCode result = IsConfigurationValid(ref parameter, inputDeviceName); + + if (result == ResultCode.Success) + { + if (inputDeviceName.Length == 0) + { + DeviceName = GetDeviceDefaultName(); + } + else + { + DeviceName = inputDeviceName; + } + + if (parameter.ChannelCount == 6) + { + ChannelCount = 6; + } + else + { + ChannelCount = 2; + } + + SampleFormat = sampleFormat; + SampleRate = Constants.TargetSampleRate; + } + + return result; + } + + /// + /// Append a new audio buffer to the audio output. + /// + /// The unique tag of this buffer. + /// The buffer informations. + /// A reporting an error or a success. + public ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer userBuffer) + { + lock (_parentLock) + { + AudioBuffer buffer = new AudioBuffer + { + BufferTag = bufferTag, + DataPointer = userBuffer.Data, + DataSize = userBuffer.DataSize + }; + + if (_session.AppendBuffer(buffer)) + { + return ResultCode.Success; + } + + return ResultCode.BufferRingFull; + } + } + + /// + /// Get the release buffers. + /// + /// The buffer to write the release buffers + /// The count of released buffers + /// A reporting an error or a success. + public ResultCode GetReleasedBuffer(Span releasedBuffers, out uint releasedCount) + { + releasedCount = 0; + + // Ensure that the first entry is set to zero if no entries are returned. + if (releasedBuffers.Length > 0) + { + releasedBuffers[0] = 0; + } + + lock (_parentLock) + { + for (int i = 0; i < releasedBuffers.Length; i++) + { + if (!_session.TryPopReleasedBuffer(out AudioBuffer buffer)) + { + break; + } + + releasedBuffers[i] = buffer.BufferTag; + releasedCount++; + } + } + + return ResultCode.Success; + } + + /// + /// Get the current state of the . + /// + /// Return the curent sta\te of the + /// + public AudioDeviceState GetState() + { + lock (_parentLock) + { + return _session.GetState(); + } + } + + /// + /// Start the audio session. + /// + /// A reporting an error or a success + public ResultCode Start() + { + lock (_parentLock) + { + return _session.Start(); + } + } + + /// + /// Stop the audio session. + /// + /// A reporting an error or a success + public ResultCode Stop() + { + lock (_parentLock) + { + return _session.Stop(); + } + } + + /// + /// Get the volume of the session. + /// + /// The volume of the session + public float GetVolume() + { + lock (_parentLock) + { + return _session.GetVolume(); + } + } + + /// + /// Set the volume of the session. + /// + /// The new volume to set + public void SetVolume(float volume) + { + lock (_parentLock) + { + _session.SetVolume(volume); + } + } + + /// + /// Get the count of buffer currently in use (server + driver side). + /// + /// The count of buffer currently in use + public uint GetBufferCount() + { + lock (_parentLock) + { + return _session.GetBufferCount(); + } + } + + /// + /// Check if a buffer is present. + /// + /// The unique tag of the buffer + /// Return true if a buffer is present + public bool ContainsBuffer(ulong bufferTag) + { + lock (_parentLock) + { + return _session.ContainsBuffer(bufferTag); + } + } + + /// + /// Get the count of sample played in this session. + /// + /// The count of sample played in this session + public ulong GetPlayedSampleCount() + { + lock (_parentLock) + { + return _session.GetPlayedSampleCount(); + } + } + + /// + /// Flush all buffers to the initial state. + /// + /// True if any buffers was flushed + public bool FlushBuffers() + { + lock (_parentLock) + { + return _session.FlushBuffers(); + } + } + + public void Dispose() + { + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _session.Dispose(); + + _manager.Unregister(this); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Common/AuxiliaryBufferAddresses.cs b/src/Ryujinx.Audio/Renderer/Common/AuxiliaryBufferAddresses.cs new file mode 100644 index 00000000..96647405 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/AuxiliaryBufferAddresses.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Common +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AuxiliaryBufferAddresses + { + public ulong SendBufferInfo; + public ulong SendBufferInfoBase; + public ulong ReturnBufferInfo; + public ulong ReturnBufferInfoBase; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs b/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs new file mode 100644 index 00000000..270f84d5 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs @@ -0,0 +1,50 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Represents the input parameter for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BehaviourParameter + { + /// + /// The current audio renderer revision in use. + /// + public int UserRevision; + + /// + /// Reserved/padding. + /// + private uint _padding; + + /// + /// The flags given controlling behaviour of the audio renderer + /// + /// See and . + public ulong Flags; + + /// + /// Represents an error during . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ErrorInfo + { + /// + /// The error code to report. + /// + public ResultCode ErrorCode; + + /// + /// Reserved/padding. + /// + private uint _padding; + + /// + /// Extra information given with the + /// + /// This is usually used to report a faulting cpu address when a mapping fail. + public ulong ExtraErrorInfo; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs b/src/Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs new file mode 100644 index 00000000..24a9350f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs @@ -0,0 +1,150 @@ +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Represents a adjacent matrix. + /// + /// This is used for splitter routing. + public class EdgeMatrix + { + /// + /// Backing used for node connections. + /// + private BitArray _storage; + + /// + /// The count of nodes of the current instance. + /// + private int _nodeCount; + + /// + /// Get the required work buffer size memory needed for the . + /// + /// The count of nodes. + /// The size required for the given . + public static int GetWorkBufferSize(int nodeCount) + { + int size = BitUtils.AlignUp(nodeCount * nodeCount, Constants.BufferAlignment); + + return size / Unsafe.SizeOf(); + } + + /// + /// Initializes the instance with backing memory. + /// + /// The backing memory. + /// The count of nodes. + public void Initialize(Memory edgeMatrixWorkBuffer, int nodeCount) + { + Debug.Assert(edgeMatrixWorkBuffer.Length >= GetWorkBufferSize(nodeCount)); + + _storage = new BitArray(edgeMatrixWorkBuffer); + + _nodeCount = nodeCount; + + _storage.Reset(); + } + + /// + /// Test if the bit at the given index is set. + /// + /// A bit index. + /// Returns true if the bit at the given index is set + public bool Test(int index) + { + return _storage.Test(index); + } + + /// + /// Reset all bits in the storage. + /// + public void Reset() + { + _storage.Reset(); + } + + /// + /// Reset the bit at the given index. + /// + /// A bit index. + public void Reset(int index) + { + _storage.Reset(index); + } + + /// + /// Set the bit at the given index. + /// + /// A bit index. + public void Set(int index) + { + _storage.Set(index); + } + + /// + /// Connect a given source to a given destination. + /// + /// The source index. + /// The destination index. + public void Connect(int source, int destination) + { + Debug.Assert(source < _nodeCount); + Debug.Assert(destination < _nodeCount); + + _storage.Set(_nodeCount * source + destination); + } + + /// + /// Check if the given source is connected to the given destination. + /// + /// The source index. + /// The destination index. + /// Returns true if the given source is connected to the given destination. + public bool Connected(int source, int destination) + { + Debug.Assert(source < _nodeCount); + Debug.Assert(destination < _nodeCount); + + return _storage.Test(_nodeCount * source + destination); + } + + /// + /// Disconnect a given source from a given destination. + /// + /// The source index. + /// The destination index. + public void Disconnect(int source, int destination) + { + Debug.Assert(source < _nodeCount); + Debug.Assert(destination < _nodeCount); + + _storage.Reset(_nodeCount * source + destination); + } + + /// + /// Remove all edges from a given source. + /// + /// The source index. + public void RemoveEdges(int source) + { + for (int i = 0; i < _nodeCount; i++) + { + Disconnect(source, i); + } + } + + /// + /// Get the total node count. + /// + /// The total node count. + public int GetNodeCount() + { + return _nodeCount; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Common/EffectType.cs b/src/Ryujinx.Audio/Renderer/Common/EffectType.cs new file mode 100644 index 00000000..7128db4c --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/EffectType.cs @@ -0,0 +1,58 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// The type of an effect. + /// + public enum EffectType : byte + { + /// + /// Invalid effect. + /// + Invalid, + + /// + /// Effect applying additional mixing capability. + /// + BufferMix, + + /// + /// Effect applying custom user effect (via auxiliary buffers). + /// + AuxiliaryBuffer, + + /// + /// Effect applying a delay. + /// + Delay, + + /// + /// Effect applying a reverberation effect via a given preset. + /// + Reverb, + + /// + /// Effect applying a 3D reverberation effect via a given preset. + /// + Reverb3d, + + /// + /// Effect applying a biquad filter. + /// + BiquadFilter, + + /// + /// Effect applying a limiter (DRC). + /// + Limiter, + + /// + /// Effect to capture mixes (via auxiliary buffers). + /// + CaptureBuffer, + + /// + /// Effect applying a compressor filter (DRC). + /// + Compressor, + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Common/MemoryPoolUserState.cs b/src/Ryujinx.Audio/Renderer/Common/MemoryPoolUserState.cs new file mode 100644 index 00000000..590731c3 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/MemoryPoolUserState.cs @@ -0,0 +1,43 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Represents the state of a memory pool. + /// + public enum MemoryPoolUserState : uint + { + /// + /// Invalid state. + /// + Invalid = 0, + + /// + /// The memory pool is new. (client side only) + /// + New = 1, + + /// + /// The user asked to detach the memory pool from the . + /// + RequestDetach = 2, + + /// + /// The memory pool is detached from the . + /// + Detached = 3, + + /// + /// The user asked to attach the memory pool to the . + /// + RequestAttach = 4, + + /// + /// The memory pool is attached to the . + /// + Attached = 5, + + /// + /// The memory pool is released. (client side only) + /// + Released = 6 + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs b/src/Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs new file mode 100644 index 00000000..a999e3ad --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Helper for manipulating node ids. + /// + public static class NodeIdHelper + { + /// + /// Get the type of a node from a given node id. + /// + /// Id of the node. + /// The type of the node. + public static NodeIdType GetType(int nodeId) + { + return (NodeIdType)(nodeId >> 28); + } + + /// + /// Get the base of a node from a given node id. + /// + /// Id of the node. + /// The base of the node. + public static int GetBase(int nodeId) + { + return (nodeId >> 16) & 0xFFF; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Common/NodeIdType.cs b/src/Ryujinx.Audio/Renderer/Common/NodeIdType.cs new file mode 100644 index 00000000..69b58f6b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/NodeIdType.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// The type of a node. + /// + public enum NodeIdType : byte + { + /// + /// Invalid node id. + /// + Invalid = 0, + + /// + /// Voice related node id. (data source, biquad filter, ...) + /// + Voice = 1, + + /// + /// Mix related node id. (mix, effects, splitters, ...) + /// + Mix = 2, + + /// + /// Sink related node id. (device & circular buffer sink) + /// + Sink = 3, + + /// + /// Performance monitoring related node id (performance commands) + /// + Performance = 15 + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Common/NodeStates.cs b/src/Ryujinx.Audio/Renderer/Common/NodeStates.cs new file mode 100644 index 00000000..45748d60 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/NodeStates.cs @@ -0,0 +1,229 @@ +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Common +{ + public class NodeStates + { + private class Stack + { + private Memory _storage; + private int _index; + + private int _nodeCount; + + public void Reset(Memory storage, int nodeCount) + { + Debug.Assert(storage.Length * sizeof(int) >= CalcBufferSize(nodeCount)); + + _storage = storage; + _index = 0; + _nodeCount = nodeCount; + } + + public int GetCurrentCount() + { + return _index; + } + + public void Push(int data) + { + Debug.Assert(_index + 1 <= _nodeCount); + + _storage.Span[_index++] = data; + } + + public int Pop() + { + Debug.Assert(_index > 0); + + return _storage.Span[--_index]; + } + + public int Top() + { + return _storage.Span[_index - 1]; + } + + public static int CalcBufferSize(int nodeCount) + { + return nodeCount * sizeof(int); + } + } + + private int _nodeCount; + private EdgeMatrix _discovered; + private EdgeMatrix _finished; + private Memory _resultArray; + private Stack _stack; + private int _tsortResultIndex; + + private enum NodeState : byte + { + Unknown, + Discovered, + Finished + } + + public NodeStates() + { + _stack = new Stack(); + _discovered = new EdgeMatrix(); + _finished = new EdgeMatrix(); + } + + public static int GetWorkBufferSize(int nodeCount) + { + return Stack.CalcBufferSize(nodeCount * nodeCount) + 0xC * nodeCount + 2 * EdgeMatrix.GetWorkBufferSize(nodeCount); + } + + public void Initialize(Memory nodeStatesWorkBuffer, int nodeCount) + { + int workBufferSize = GetWorkBufferSize(nodeCount); + + Debug.Assert(nodeStatesWorkBuffer.Length >= workBufferSize); + + _nodeCount = nodeCount; + + int edgeMatrixWorkBufferSize = EdgeMatrix.GetWorkBufferSize(nodeCount); + + _discovered.Initialize(nodeStatesWorkBuffer.Slice(0, edgeMatrixWorkBufferSize), nodeCount); + _finished.Initialize(nodeStatesWorkBuffer.Slice(edgeMatrixWorkBufferSize, edgeMatrixWorkBufferSize), nodeCount); + + nodeStatesWorkBuffer = nodeStatesWorkBuffer.Slice(edgeMatrixWorkBufferSize * 2); + + _resultArray = SpanMemoryManager.Cast(nodeStatesWorkBuffer.Slice(0, sizeof(int) * nodeCount)); + + nodeStatesWorkBuffer = nodeStatesWorkBuffer.Slice(sizeof(int) * nodeCount); + + Memory stackWorkBuffer = SpanMemoryManager.Cast(nodeStatesWorkBuffer.Slice(0, Stack.CalcBufferSize(nodeCount * nodeCount))); + + _stack.Reset(stackWorkBuffer, nodeCount * nodeCount); + } + + private void Reset() + { + _discovered.Reset(); + _finished.Reset(); + _tsortResultIndex = 0; + _resultArray.Span.Fill(-1); + } + + private NodeState GetState(int index) + { + Debug.Assert(index < _nodeCount); + + if (_discovered.Test(index)) + { + Debug.Assert(!_finished.Test(index)); + + return NodeState.Discovered; + } + else if (_finished.Test(index)) + { + Debug.Assert(!_discovered.Test(index)); + + return NodeState.Finished; + } + + return NodeState.Unknown; + } + + private void SetState(int index, NodeState state) + { + switch (state) + { + case NodeState.Unknown: + _discovered.Reset(index); + _finished.Reset(index); + break; + case NodeState.Discovered: + _discovered.Set(index); + _finished.Reset(index); + break; + case NodeState.Finished: + _finished.Set(index); + _discovered.Reset(index); + break; + } + } + + private void PushTsortResult(int index) + { + Debug.Assert(index < _nodeCount); + + _resultArray.Span[_tsortResultIndex++] = index; + } + + public ReadOnlySpan GetTsortResult() + { + return _resultArray.Span.Slice(0, _tsortResultIndex); + } + + public bool Sort(EdgeMatrix edgeMatrix) + { + Reset(); + + if (_nodeCount <= 0) + { + return true; + } + + for (int i = 0; i < _nodeCount; i++) + { + if (GetState(i) == NodeState.Unknown) + { + _stack.Push(i); + } + + while (_stack.GetCurrentCount() > 0) + { + int topIndex = _stack.Top(); + + NodeState topState = GetState(topIndex); + + if (topState == NodeState.Discovered) + { + SetState(topIndex, NodeState.Finished); + PushTsortResult(topIndex); + _stack.Pop(); + } + else if (topState == NodeState.Finished) + { + _stack.Pop(); + } + else + { + if (topState == NodeState.Unknown) + { + SetState(topIndex, NodeState.Discovered); + } + + for (int j = 0; j < edgeMatrix.GetNodeCount(); j++) + { + if (edgeMatrix.Connected(topIndex, j)) + { + NodeState jState = GetState(j); + + if (jState == NodeState.Unknown) + { + _stack.Push(j); + } + // Found a loop, reset and propagate rejection. + else if (jState == NodeState.Discovered) + { + Reset(); + + return false; + } + } + } + } + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs b/src/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs new file mode 100644 index 00000000..805d5518 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + public enum PerformanceDetailType : byte + { + Unknown, + PcmInt16, + Adpcm, + VolumeRamp, + BiquadFilter, + Mix, + Delay, + Aux, + Reverb, + Reverb3d, + PcmFloat, + Limiter, + CaptureBuffer, + Compressor + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Common/PerformanceEntryType.cs b/src/Ryujinx.Audio/Renderer/Common/PerformanceEntryType.cs new file mode 100644 index 00000000..bde72aae --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/PerformanceEntryType.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + public enum PerformanceEntryType : byte + { + Invalid, + Voice, + SubMix, + FinalMix, + Sink + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Common/PlayState.cs b/src/Ryujinx.Audio/Renderer/Common/PlayState.cs new file mode 100644 index 00000000..4a6929e0 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/PlayState.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Common play state. + /// + public enum PlayState : byte + { + /// + /// The user request the voice to be started. + /// + Start, + + /// + /// The user request the voice to be stopped. + /// + Stop, + + /// + /// The user request the voice to be paused. + /// + Pause + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Common/ReverbEarlyMode.cs b/src/Ryujinx.Audio/Renderer/Common/ReverbEarlyMode.cs new file mode 100644 index 00000000..aa768562 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/ReverbEarlyMode.cs @@ -0,0 +1,33 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Early reverb reflection. + /// + public enum ReverbEarlyMode : uint + { + /// + /// Room early reflection. (small acoustic space, fast reflection) + /// + Room, + + /// + /// Chamber early reflection. (bigger than 's acoustic space, short reflection) + /// + Chamber, + + /// + /// Hall early reflection. (large acoustic space, warm reflection) + /// + Hall, + + /// + /// Cathedral early reflection. (very large acoustic space, pronounced bright reflection) + /// + Cathedral, + + /// + /// No early reflection. + /// + Disabled + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Common/ReverbLateMode.cs b/src/Ryujinx.Audio/Renderer/Common/ReverbLateMode.cs new file mode 100644 index 00000000..8aa88165 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/ReverbLateMode.cs @@ -0,0 +1,38 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Late reverb reflection. + /// + public enum ReverbLateMode : uint + { + /// + /// Room late reflection. (small acoustic space, fast reflection) + /// + Room, + + /// + /// Hall late reflection. (large acoustic space, warm reflection) + /// + Hall, + + /// + /// Classic plate late reflection. (clean distinctive reverb) + /// + Plate, + + /// + /// Cathedral late reflection. (very large acoustic space, pronounced bright reflection) + /// + Cathedral, + + /// + /// Do not apply any delay. (max delay) + /// + NoDelay, + + /// + /// Max delay. (used for delay line limits) + /// + Limit = NoDelay + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Common/SinkType.cs b/src/Ryujinx.Audio/Renderer/Common/SinkType.cs new file mode 100644 index 00000000..2e17201e --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/SinkType.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// The type of a sink. + /// + public enum SinkType : byte + { + /// + /// The sink is in an invalid state. + /// + Invalid, + + /// + /// The sink is a device. + /// + Device, + + /// + /// The sink is a circular buffer. + /// + CircularBuffer + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs b/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs new file mode 100644 index 00000000..70dbfa94 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs @@ -0,0 +1,33 @@ +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Update data header used for input and output of . + /// + public struct UpdateDataHeader + { + public int Revision; + public uint BehaviourSize; + public uint MemoryPoolsSize; + public uint VoicesSize; + public uint VoiceResourcesSize; + public uint EffectsSize; + public uint MixesSize; + public uint SinksSize; + public uint PerformanceBufferSize; + public uint Unknown24; + public uint RenderInfoSize; + + private unsafe fixed int _reserved[4]; + + public uint TotalSize; + + public void Initialize(int revision) + { + Revision = revision; + + TotalSize = (uint)Unsafe.SizeOf(); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs b/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs new file mode 100644 index 00000000..f52c2f4c --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs @@ -0,0 +1,104 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// Represent the update state of a voice. + /// + /// This is shared between the server and audio processor. + [StructLayout(LayoutKind.Sequential, Pack = Align)] + public struct VoiceUpdateState + { + public const int Align = 0x10; + public const int BiquadStateOffset = 0x0; + public const int BiquadStateSize = 0x10; + + /// + /// The state of the biquad filters of this voice. + /// + public Array2 BiquadFilterState; + + /// + /// The total amount of samples that was played. + /// + /// This is reset to 0 when a finishes playing and is set. + /// This is reset to 0 when looping while is set. + public ulong PlayedSampleCount; + + /// + /// The current sample offset in the pointed by . + /// + public int Offset; + + /// + /// The current index of the in use. + /// + public uint WaveBufferIndex; + + private WaveBufferValidArray _isWaveBufferValid; + + /// + /// The total amount of consumed. + /// + public uint WaveBufferConsumed; + + /// + /// Pitch used for Sample Rate Conversion. + /// + public Array8 Pitch; + + public float Fraction; + + /// + /// The ADPCM loop context when is in use. + /// + public AdpcmLoopContext LoopContext; + + /// + /// The last samples after a mix ramp. + /// + /// This is used for depop (to perform voice drop). + public Array24 LastSamples; + + /// + /// The current count of loop performed. + /// + public int LoopCount; + + [StructLayout(LayoutKind.Sequential, Size = 1 * Constants.VoiceWaveBufferCount, Pack = 1)] + private struct WaveBufferValidArray { } + + /// + /// Contains information of validity. + /// + public Span IsWaveBufferValid => SpanHelpers.AsSpan(ref _isWaveBufferValid); + + /// + /// Mark the current as played and switch to the next one. + /// + /// The current + /// The wavebuffer index. + /// The amount of wavebuffers consumed. + /// The total count of sample played. + public void MarkEndOfBufferWaveBufferProcessing(ref WaveBuffer waveBuffer, ref int waveBufferIndex, ref uint waveBufferConsumed, ref ulong playedSampleCount) + { + IsWaveBufferValid[waveBufferIndex++] = false; + LoopCount = 0; + waveBufferConsumed++; + + if (waveBufferIndex >= Constants.VoiceWaveBufferCount) + { + waveBufferIndex = 0; + } + + if (waveBuffer.IsEndOfStream) + { + playedSampleCount = 0; + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Common/WaveBuffer.cs b/src/Ryujinx.Audio/Renderer/Common/WaveBuffer.cs new file mode 100644 index 00000000..0d00e838 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/WaveBuffer.cs @@ -0,0 +1,82 @@ +using System.Runtime.InteropServices; + +using DspAddr = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Common +{ + /// + /// A wavebuffer used for data source commands. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct WaveBuffer + { + /// + /// The DSP address of the sample data of the wavebuffer. + /// + public DspAddr Buffer; + + /// + /// The DSP address of the context of the wavebuffer. + /// + /// Only used by . + public DspAddr Context; + + /// + /// The size of the sample buffer data. + /// + public uint BufferSize; + + /// + /// The size of the context buffer. + /// + public uint ContextSize; + + /// + /// First sample to play on the wavebuffer. + /// + public uint StartSampleOffset; + + /// + /// Last sample to play on the wavebuffer. + /// + public uint EndSampleOffset; + + /// + /// First sample to play when looping the wavebuffer. + /// + /// + /// If or is equal to zero,, it will default to and . + /// + public uint LoopStartSampleOffset; + + /// + /// Last sample to play when looping the wavebuffer. + /// + /// + /// If or is equal to zero, it will default to and . + /// + public uint LoopEndSampleOffset; + + /// + /// The max loop count. + /// + public int LoopCount; + + /// + /// Set to true if the wavebuffer is looping. + /// + [MarshalAs(UnmanagedType.I1)] + public bool Looping; + + /// + /// Set to true if the wavebuffer is the end of stream. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsEndOfStream; + + /// + /// Padding/Reserved. + /// + private ushort _padding; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Common/WorkBufferAllocator.cs b/src/Ryujinx.Audio/Renderer/Common/WorkBufferAllocator.cs new file mode 100644 index 00000000..f35dbec7 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Common/WorkBufferAllocator.cs @@ -0,0 +1,61 @@ +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Common +{ + public class WorkBufferAllocator + { + public Memory BackingMemory { get; } + + public ulong Offset { get; private set; } + + public WorkBufferAllocator(Memory backingMemory) + { + BackingMemory = backingMemory; + } + + public Memory Allocate(ulong size, int align) + { + Debug.Assert(align != 0); + + if (size != 0) + { + ulong alignedOffset = BitUtils.AlignUp(Offset, (ulong)align); + + if (alignedOffset + size <= (ulong)BackingMemory.Length) + { + Memory result = BackingMemory.Slice((int)alignedOffset, (int)size); + + Offset = alignedOffset + size; + + // Clear the memory to be sure that is does not contain any garbage. + result.Span.Fill(0); + + return result; + } + } + + return Memory.Empty; + } + + public Memory Allocate(ulong count, int align) where T : unmanaged + { + Memory allocatedMemory = Allocate((ulong)Unsafe.SizeOf() * count, align); + + if (allocatedMemory.IsEmpty) + { + return Memory.Empty; + } + + return SpanMemoryManager.Cast(allocatedMemory); + } + + public static ulong GetTargetSize(ulong currentSize, ulong count, int align) where T : unmanaged + { + return BitUtils.AlignUp(currentSize, (ulong)align) + (ulong)Unsafe.SizeOf() * count; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs b/src/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs new file mode 100644 index 00000000..2fa030a8 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs @@ -0,0 +1,89 @@ +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Device +{ + /// + /// Represents a virtual device used by IAudioDevice. + /// + public class VirtualDevice + { + /// + /// All the defined virtual devices. + /// + public static readonly VirtualDevice[] Devices = new VirtualDevice[5] + { + new VirtualDevice("AudioStereoJackOutput", 2, true), + new VirtualDevice("AudioBuiltInSpeakerOutput", 2, false), + new VirtualDevice("AudioTvOutput", 6, false), + new VirtualDevice("AudioUsbDeviceOutput", 2, true), + new VirtualDevice("AudioExternalOutput", 6, true), + }; + + /// + /// The name of the . + /// + public string Name { get; } + + /// + /// The count of channels supported by the . + /// + public uint ChannelCount { get; } + + /// + /// The system master volume of the . + /// + public float MasterVolume { get; private set; } + + /// + /// Define if the is provided by an external interface. + /// + public bool IsExternalOutput { get; } + + /// + /// Create a new instance. + /// + /// The name of the . + /// The count of channels supported by the . + /// Indicate if the is provided by an external interface. + private VirtualDevice(string name, uint channelCount, bool isExternalOutput) + { + Name = name; + ChannelCount = channelCount; + IsExternalOutput = isExternalOutput; + } + + /// + /// Update the master volume of the . + /// + /// The new master volume. + public void UpdateMasterVolume(float volume) + { + Debug.Assert(volume >= 0.0f && volume <= 1.0f); + + MasterVolume = volume; + } + + /// + /// Check if the is a usb device. + /// + /// Returns true if the is a usb device. + public bool IsUsbDevice() + { + return Name.Equals("AudioUsbDeviceOutput"); + } + + /// + /// Get the output device name of the . + /// + /// The output device name of the . + public string GetOutputDeviceName() + { + if (IsExternalOutput) + { + return "AudioExternalOutput"; + } + + return Name; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSession.cs b/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSession.cs new file mode 100644 index 00000000..db35d26d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSession.cs @@ -0,0 +1,27 @@ +namespace Ryujinx.Audio.Renderer.Device +{ + /// + /// Represents a virtual device session used by IAudioDevice. + /// + public class VirtualDeviceSession + { + /// + /// The associated to this session. + /// + public VirtualDevice Device { get; } + + /// + /// The user volume of this session. + /// + public float Volume { get; set; } + + /// + /// Create a new instance. + /// + /// The associated to this session. + public VirtualDeviceSession(VirtualDevice virtualDevice) + { + Device = virtualDevice; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSessionRegistry.cs b/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSessionRegistry.cs new file mode 100644 index 00000000..927e45ad --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Device/VirtualDeviceSessionRegistry.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; + +namespace Ryujinx.Audio.Renderer.Device +{ + /// + /// Represent an instance containing a registry of . + /// + public class VirtualDeviceSessionRegistry + { + /// + /// The session registry, used to store the sessions of a given AppletResourceId. + /// + private Dictionary _sessionsRegistry = new Dictionary(); + + /// + /// The default . + /// + /// This is used when the USB device is the default one on older revision. + public VirtualDevice DefaultDevice => VirtualDevice.Devices[0]; + + /// + /// The current active . + /// + // TODO: make this configurable + public VirtualDevice ActiveDevice = VirtualDevice.Devices[2]; + + /// + /// Get the associated from an AppletResourceId. + /// + /// The AppletResourceId used. + /// The associated from an AppletResourceId. + public VirtualDeviceSession[] GetSessionByAppletResourceId(ulong resourceAppletId) + { + if (_sessionsRegistry.TryGetValue(resourceAppletId, out VirtualDeviceSession[] result)) + { + return result; + } + + result = CreateSessionsFromBehaviourContext(); + + _sessionsRegistry.Add(resourceAppletId, result); + + return result; + } + + /// + /// Create a new array of sessions for each . + /// + /// A new array of sessions for each . + private static VirtualDeviceSession[] CreateSessionsFromBehaviourContext() + { + VirtualDeviceSession[] virtualDeviceSession = new VirtualDeviceSession[VirtualDevice.Devices.Length]; + + for (int i = 0; i < virtualDeviceSession.Length; i++) + { + virtualDeviceSession[i] = new VirtualDeviceSession(VirtualDevice.Devices[i]); + } + + return virtualDeviceSession; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs new file mode 100644 index 00000000..2680dcb1 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs @@ -0,0 +1,216 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Common.Logging; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class AdpcmHelper + { + private const int FixedPointPrecision = 11; + private const int SamplesPerFrame = 14; + private const int NibblesPerFrame = SamplesPerFrame + 2; + private const int BytesPerFrame = 8; + private const int BitsPerFrame = BytesPerFrame * 8; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint GetAdpcmDataSize(int sampleCount) + { + Debug.Assert(sampleCount >= 0); + + int frames = sampleCount / SamplesPerFrame; + int extraSize = 0; + + if ((sampleCount % SamplesPerFrame) != 0) + { + extraSize = (sampleCount % SamplesPerFrame) / 2 + 1 + (sampleCount % 2); + } + + return (uint)(BytesPerFrame * frames + extraSize); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetAdpcmOffsetFromSampleOffset(int sampleOffset) + { + Debug.Assert(sampleOffset >= 0); + + return GetNibblesFromSampleCount(sampleOffset) / 2; + } + + public static int NibbleToSample(int nibble) + { + int frames = nibble / NibblesPerFrame; + int extraNibbles = nibble % NibblesPerFrame; + int samples = SamplesPerFrame * frames; + + return samples + extraNibbles - 2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetNibblesFromSampleCount(int sampleCount) + { + byte headerSize = 0; + + if ((sampleCount % SamplesPerFrame) != 0) + { + headerSize = 2; + } + + return sampleCount % SamplesPerFrame + NibblesPerFrame * (sampleCount / SamplesPerFrame) + headerSize; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static short Saturate(int value) + { + if (value > short.MaxValue) + value = short.MaxValue; + + if (value < short.MinValue) + value = short.MinValue; + + return (short)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static short GetCoefficientAtIndex(ReadOnlySpan coefficients, int index) + { + if ((uint)index > (uint)coefficients.Length) + { + Logger.Error?.Print(LogClass.AudioRenderer, $"Out of bound read for coefficient at index {index}"); + + return 0; + } + + return coefficients[index]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Decode(Span output, ReadOnlySpan input, int startSampleOffset, int endSampleOffset, int offset, int count, ReadOnlySpan coefficients, ref AdpcmLoopContext loopContext) + { + if (input.IsEmpty || endSampleOffset < startSampleOffset) + { + return 0; + } + + byte predScale = (byte)loopContext.PredScale; + byte scale = (byte)(predScale & 0xF); + byte coefficientIndex = (byte)((predScale >> 4) & 0xF); + short history0 = loopContext.History0; + short history1 = loopContext.History1; + short coefficient0 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 0); + short coefficient1 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 1); + + int decodedCount = Math.Min(count, endSampleOffset - startSampleOffset - offset); + int nibbles = GetNibblesFromSampleCount(offset + startSampleOffset); + int remaining = decodedCount; + int outputBufferIndex = 0; + int inputIndex = 0; + + ReadOnlySpan targetInput; + + targetInput = input.Slice(nibbles / 2); + + while (remaining > 0) + { + int samplesCount; + + if (((uint)nibbles % NibblesPerFrame) == 0) + { + predScale = targetInput[inputIndex++]; + + scale = (byte)(predScale & 0xF); + + coefficientIndex = (byte)((predScale >> 4) & 0xF); + + coefficient0 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2); + coefficient1 = GetCoefficientAtIndex(coefficients, coefficientIndex * 2 + 1); + + nibbles += 2; + + samplesCount = Math.Min(remaining, SamplesPerFrame); + } + else + { + samplesCount = 1; + } + + int scaleFixedPoint = FixedPointHelper.ToFixed(1.0f, FixedPointPrecision) << scale; + + if (samplesCount < SamplesPerFrame) + { + for (int i = 0; i < samplesCount; i++) + { + int value = targetInput[inputIndex]; + + int sample; + + if ((nibbles & 1) != 0) + { + sample = (value << 28) >> 28; + + inputIndex++; + } + else + { + sample = (value << 24) >> 28; + } + + nibbles++; + + int prediction = coefficient0 * history0 + coefficient1 * history1; + + sample = FixedPointHelper.RoundUpAndToInt(sample * scaleFixedPoint + prediction, FixedPointPrecision); + + short saturatedSample = Saturate(sample); + + history1 = history0; + history0 = saturatedSample; + + output[outputBufferIndex++] = saturatedSample; + + remaining--; + } + } + else + { + for (int i = 0; i < SamplesPerFrame / 2; i++) + { + int value = targetInput[inputIndex]; + + int sample0; + int sample1; + + sample0 = (value << 24) >> 28; + sample1 = (value << 28) >> 28; + + inputIndex++; + + int prediction0 = coefficient0 * history0 + coefficient1 * history1; + sample0 = FixedPointHelper.RoundUpAndToInt(sample0 * scaleFixedPoint + prediction0, FixedPointPrecision); + short saturatedSample0 = Saturate(sample0); + + int prediction1 = coefficient0 * saturatedSample0 + coefficient1 * history0; + sample1 = FixedPointHelper.RoundUpAndToInt(sample1 * scaleFixedPoint + prediction1, FixedPointPrecision); + short saturatedSample1 = Saturate(sample1); + + history1 = saturatedSample0; + history0 = saturatedSample1; + + output[outputBufferIndex++] = saturatedSample0; + output[outputBufferIndex++] = saturatedSample1; + } + + nibbles += SamplesPerFrame; + remaining -= SamplesPerFrame; + } + } + + loopContext.PredScale = predScale; + loopContext.History0 = history0; + loopContext.History1 = history1; + + return decodedCount; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs b/src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs new file mode 100644 index 00000000..7bd0443c --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs @@ -0,0 +1,276 @@ +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Renderer.Dsp.Command; +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using System; +using System.Threading; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public class AudioProcessor : IDisposable + { + private const int MaxBufferedFrames = 5; + private const int TargetBufferedFrames = 3; + + private enum MailboxMessage : uint + { + Start, + Stop, + RenderStart, + RenderEnd + } + + private class RendererSession + { + public CommandList CommandList; + public int RenderingLimit; + public ulong AppletResourceId; + } + + private Mailbox _mailbox; + private RendererSession[] _sessionCommandList; + private Thread _workerThread; + + public IHardwareDevice[] OutputDevices { get; private set; } + + private long _lastTime; + private long _playbackEnds; + private ManualResetEvent _event; + + private ManualResetEvent _pauseEvent; + + public AudioProcessor() + { + _event = new ManualResetEvent(false); + } + + private static uint GetHardwareChannelCount(IHardwareDeviceDriver deviceDriver) + { + // Get the real device driver (In case the compat layer is on top of it). + deviceDriver = deviceDriver.GetRealDeviceDriver(); + + if (deviceDriver.SupportsChannelCount(6)) + { + return 6; + } + else + { + // NOTE: We default to stereo as this will get downmixed to mono by the compat layer if it's not compatible. + return 2; + } + } + + public void Start(IHardwareDeviceDriver deviceDriver, float volume) + { + OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax]; + + // TODO: Before enabling this, we need up-mixing from stereo to 5.1. + // uint channelCount = GetHardwareChannelCount(deviceDriver); + uint channelCount = 2; + + for (int i = 0; i < OutputDevices.Length; i++) + { + // TODO: Don't hardcode sample rate. + OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate, volume); + } + + _mailbox = new Mailbox(); + _sessionCommandList = new RendererSession[Constants.AudioRendererSessionCountMax]; + _event.Reset(); + _lastTime = PerformanceCounter.ElapsedNanoseconds; + _pauseEvent = deviceDriver.GetPauseEvent(); + + StartThread(); + + _mailbox.SendMessage(MailboxMessage.Start); + + if (_mailbox.ReceiveResponse() != MailboxMessage.Start) + { + throw new InvalidOperationException("Audio Processor Start response was invalid!"); + } + } + + public void Stop() + { + _mailbox.SendMessage(MailboxMessage.Stop); + + if (_mailbox.ReceiveResponse() != MailboxMessage.Stop) + { + throw new InvalidOperationException("Audio Processor Stop response was invalid!"); + } + + foreach (IHardwareDevice device in OutputDevices) + { + device.Dispose(); + } + } + + public void Send(int sessionId, CommandList commands, int renderingLimit, ulong appletResourceId) + { + _sessionCommandList[sessionId] = new RendererSession + { + CommandList = commands, + RenderingLimit = renderingLimit, + AppletResourceId = appletResourceId + }; + } + + public bool HasRemainingCommands(int sessionId) + { + return _sessionCommandList[sessionId] != null; + } + + public void Signal() + { + _mailbox.SendMessage(MailboxMessage.RenderStart); + } + + public void Wait() + { + if (_mailbox.ReceiveResponse() != MailboxMessage.RenderEnd) + { + throw new InvalidOperationException("Audio Processor Wait response was invalid!"); + } + + long increment = Constants.AudioProcessorMaxUpdateTimeTarget; + + long timeNow = PerformanceCounter.ElapsedNanoseconds; + + if (timeNow > _playbackEnds) + { + // Playback has restarted. + _playbackEnds = timeNow; + } + + _playbackEnds += increment; + + // The number of frames we are behind where the timer says we should be. + long framesBehind = (timeNow - _lastTime) / increment; + + // The number of frames yet to play on the backend. + long bufferedFrames = (_playbackEnds - timeNow) / increment + framesBehind; + + // If we've entered a situation where a lot of buffers will be queued on the backend, + // Skip some audio frames so that playback can catch up. + if (bufferedFrames > MaxBufferedFrames) + { + // Skip a few frames so that we're not too far behind. (the target number of frames) + _lastTime += increment * (bufferedFrames - TargetBufferedFrames); + } + + while (timeNow < _lastTime + increment) + { + _event.WaitOne(1); + + timeNow = PerformanceCounter.ElapsedNanoseconds; + } + + _lastTime += increment; + } + + private void StartThread() + { + _workerThread = new Thread(Work) + { + Name = "AudioProcessor.Worker" + }; + + _workerThread.Start(); + } + + private void Work() + { + if (_mailbox.ReceiveMessage() != MailboxMessage.Start) + { + throw new InvalidOperationException("Audio Processor Start message was invalid!"); + } + + _mailbox.SendResponse(MailboxMessage.Start); + _mailbox.SendResponse(MailboxMessage.RenderEnd); + + Logger.Info?.Print(LogClass.AudioRenderer, "Starting audio processor"); + + while (true) + { + _pauseEvent?.WaitOne(); + + MailboxMessage message = _mailbox.ReceiveMessage(); + + if (message == MailboxMessage.Stop) + { + break; + } + + if (message == MailboxMessage.RenderStart) + { + long startTicks = PerformanceCounter.ElapsedNanoseconds; + + for (int i = 0; i < _sessionCommandList.Length; i++) + { + if (_sessionCommandList[i] != null) + { + _sessionCommandList[i].CommandList.Process(OutputDevices[i]); + _sessionCommandList[i].CommandList.Dispose(); + _sessionCommandList[i] = null; + } + } + + long endTicks = PerformanceCounter.ElapsedNanoseconds; + + long elapsedTime = endTicks - startTicks; + + if (Constants.AudioProcessorMaxUpdateTime < elapsedTime) + { + Logger.Debug?.Print(LogClass.AudioRenderer, $"DSP too slow (exceeded by {elapsedTime - Constants.AudioProcessorMaxUpdateTime}ns)"); + } + + _mailbox.SendResponse(MailboxMessage.RenderEnd); + } + } + + Logger.Info?.Print(LogClass.AudioRenderer, "Stopping audio processor"); + _mailbox.SendResponse(MailboxMessage.Stop); + } + + public float GetVolume() + { + if (OutputDevices != null) + { + foreach (IHardwareDevice outputDevice in OutputDevices) + { + if (outputDevice != null) + { + return outputDevice.GetVolume(); + } + } + } + + return 0f; + } + + public void SetVolume(float volume) + { + if (OutputDevices != null) + { + foreach (IHardwareDevice outputDevice in OutputDevices) + { + outputDevice?.SetVolume(volume); + } + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _event.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs new file mode 100644 index 00000000..98460ff1 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/BiquadFilterHelper.cs @@ -0,0 +1,83 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class BiquadFilterHelper + { + private const int FixedPointPrecisionForParameter = 14; + + /// + /// Apply a single biquad filter. + /// + /// This is implemented with a direct form 2. + /// The biquad filter parameter + /// The biquad filter state + /// The output buffer to write the result + /// The input buffer to write the result + /// The count of samples to process + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ProcessBiquadFilter(ref BiquadFilterParameter parameter, ref BiquadFilterState state, Span outputBuffer, ReadOnlySpan inputBuffer, uint sampleCount) + { + float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter); + float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter); + float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter); + + float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter); + float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter); + + for (int i = 0; i < sampleCount; i++) + { + float input = inputBuffer[i]; + float output = input * a0 + state.State0; + + state.State0 = input * a1 + output * b1 + state.State1; + state.State1 = input * a2 + output * b2; + + outputBuffer[i] = output; + } + } + + /// + /// Apply multiple biquad filter. + /// + /// This is implemented with a direct form 1. + /// The biquad filter parameter + /// The biquad filter state + /// The output buffer to write the result + /// The input buffer to write the result + /// The count of samples to process + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ProcessBiquadFilter(ReadOnlySpan parameters, Span states, Span outputBuffer, ReadOnlySpan inputBuffer, uint sampleCount) + { + for (int stageIndex = 0; stageIndex < parameters.Length; stageIndex++) + { + BiquadFilterParameter parameter = parameters[stageIndex]; + + ref BiquadFilterState state = ref states[stageIndex]; + + float a0 = FixedPointHelper.ToFloat(parameter.Numerator[0], FixedPointPrecisionForParameter); + float a1 = FixedPointHelper.ToFloat(parameter.Numerator[1], FixedPointPrecisionForParameter); + float a2 = FixedPointHelper.ToFloat(parameter.Numerator[2], FixedPointPrecisionForParameter); + + float b1 = FixedPointHelper.ToFloat(parameter.Denominator[0], FixedPointPrecisionForParameter); + float b2 = FixedPointHelper.ToFloat(parameter.Denominator[1], FixedPointPrecisionForParameter); + + for (int i = 0; i < sampleCount; i++) + { + float input = inputBuffer[i]; + float output = input * a0 + state.State0 * a1 + state.State1 * a2 + state.State2 * b1 + state.State3 * b2; + + state.State1 = state.State0; + state.State0 = input; + state.State3 = state.State2; + state.State2 = output; + + outputBuffer[i] = output; + } + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs new file mode 100644 index 00000000..1fe6069f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs @@ -0,0 +1,75 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using System; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class AdpcmDataSourceCommandVersion1 : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.AdpcmDataSourceVersion1; + + public uint EstimatedProcessingTime { get; set; } + + public ushort OutputBufferIndex { get; } + public uint SampleRate { get; } + + public float Pitch { get; } + + public WaveBuffer[] WaveBuffers { get; } + + public Memory State { get; } + + public ulong AdpcmParameter { get; } + public ulong AdpcmParameterSize { get; } + + public DecodingBehaviour DecodingBehaviour { get; } + + public AdpcmDataSourceCommandVersion1(ref Server.Voice.VoiceState serverState, Memory state, ushort outputBufferIndex, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + OutputBufferIndex = outputBufferIndex; + SampleRate = serverState.SampleRate; + Pitch = serverState.Pitch; + + WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount]; + + for (int i = 0; i < WaveBuffers.Length; i++) + { + ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i]; + + WaveBuffers[i] = voiceWaveBuffer.ToCommon(1); + } + + AdpcmParameter = serverState.DataSourceStateAddressInfo.GetReference(true); + AdpcmParameterSize = serverState.DataSourceStateAddressInfo.Size; + State = state; + DecodingBehaviour = serverState.DecodingBehaviour; + } + + public void Process(CommandList context) + { + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + DataSourceHelper.WaveBufferInformation info = new DataSourceHelper.WaveBufferInformation + { + SourceSampleRate = SampleRate, + SampleFormat = SampleFormat.Adpcm, + Pitch = Pitch, + DecodingBehaviour = DecodingBehaviour, + ExtraParameter = AdpcmParameter, + ExtraParameterSize = AdpcmParameterSize, + ChannelIndex = 0, + ChannelCount = 1, + }; + + DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, ref info, WaveBuffers, ref State.Span[0], context.SampleRate, (int)context.SampleCount); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs new file mode 100644 index 00000000..5c3c0324 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs @@ -0,0 +1,173 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Renderer.Dsp.State.AuxiliaryBufferHeader; +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class AuxiliaryBufferCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.AuxiliaryBuffer; + + public uint EstimatedProcessingTime { get; set; } + + public uint InputBufferIndex { get; } + public uint OutputBufferIndex { get; } + + public AuxiliaryBufferAddresses BufferInfo { get; } + + public CpuAddress InputBuffer { get; } + public CpuAddress OutputBuffer { get; } + public uint CountMax { get; } + public uint UpdateCount { get; } + public uint WriteOffset { get; } + + public bool IsEffectEnabled { get; } + + public AuxiliaryBufferCommand(uint bufferOffset, byte inputBufferOffset, byte outputBufferOffset, + ref AuxiliaryBufferAddresses sendBufferInfo, bool isEnabled, uint countMax, + CpuAddress outputBuffer, CpuAddress inputBuffer, uint updateCount, uint writeOffset, int nodeId) + { + Enabled = true; + NodeId = nodeId; + InputBufferIndex = bufferOffset + inputBufferOffset; + OutputBufferIndex = bufferOffset + outputBufferOffset; + BufferInfo = sendBufferInfo; + InputBuffer = inputBuffer; + OutputBuffer = outputBuffer; + CountMax = countMax; + UpdateCount = updateCount; + WriteOffset = writeOffset; + IsEffectEnabled = isEnabled; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private uint Read(IVirtualMemoryManager memoryManager, ulong bufferAddress, uint countMax, Span outBuffer, uint count, uint readOffset, uint updateCount) + { + if (countMax == 0 || bufferAddress == 0) + { + return 0; + } + + uint targetReadOffset = readOffset + AuxiliaryBufferInfo.GetReadOffset(memoryManager, BufferInfo.ReturnBufferInfo); + + if (targetReadOffset > countMax) + { + return 0; + } + + uint remaining = count; + + uint outBufferOffset = 0; + + while (remaining != 0) + { + uint countToWrite = Math.Min(countMax - targetReadOffset, remaining); + + memoryManager.Read(bufferAddress + targetReadOffset * sizeof(int), MemoryMarshal.Cast(outBuffer.Slice((int)outBufferOffset, (int)countToWrite))); + + targetReadOffset = (targetReadOffset + countToWrite) % countMax; + remaining -= countToWrite; + outBufferOffset += countToWrite; + } + + if (updateCount != 0) + { + uint newReadOffset = (AuxiliaryBufferInfo.GetReadOffset(memoryManager, BufferInfo.ReturnBufferInfo) + updateCount) % countMax; + + AuxiliaryBufferInfo.SetReadOffset(memoryManager, BufferInfo.ReturnBufferInfo, newReadOffset); + } + + return count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private uint Write(IVirtualMemoryManager memoryManager, ulong outBufferAddress, uint countMax, ReadOnlySpan buffer, uint count, uint writeOffset, uint updateCount) + { + if (countMax == 0 || outBufferAddress == 0) + { + return 0; + } + + uint targetWriteOffset = writeOffset + AuxiliaryBufferInfo.GetWriteOffset(memoryManager, BufferInfo.SendBufferInfo); + + if (targetWriteOffset > countMax) + { + return 0; + } + + uint remaining = count; + + uint inBufferOffset = 0; + + while (remaining != 0) + { + uint countToWrite = Math.Min(countMax - targetWriteOffset, remaining); + + memoryManager.Write(outBufferAddress + targetWriteOffset * sizeof(int), MemoryMarshal.Cast(buffer.Slice((int)inBufferOffset, (int)countToWrite))); + + targetWriteOffset = (targetWriteOffset + countToWrite) % countMax; + remaining -= countToWrite; + inBufferOffset += countToWrite; + } + + if (updateCount != 0) + { + uint newWriteOffset = (AuxiliaryBufferInfo.GetWriteOffset(memoryManager, BufferInfo.SendBufferInfo) + updateCount) % countMax; + + AuxiliaryBufferInfo.SetWriteOffset(memoryManager, BufferInfo.SendBufferInfo, newWriteOffset); + } + + return count; + } + + public void Process(CommandList context) + { + Span inputBuffer = context.GetBuffer((int)InputBufferIndex); + Span outputBuffer = context.GetBuffer((int)OutputBufferIndex); + + if (IsEffectEnabled) + { + Span inputBufferInt = MemoryMarshal.Cast(inputBuffer); + Span outputBufferInt = MemoryMarshal.Cast(outputBuffer); + + // Convert input data to the target format for user (int) + DataSourceHelper.ToInt(inputBufferInt, inputBuffer, inputBuffer.Length); + + // Send the input to the user + Write(context.MemoryManager, OutputBuffer, CountMax, inputBufferInt, context.SampleCount, WriteOffset, UpdateCount); + + // Convert back to float just in case it's reused + DataSourceHelper.ToFloat(inputBuffer, inputBufferInt, inputBuffer.Length); + + // Retrieve the input from user + uint readResult = Read(context.MemoryManager, InputBuffer, CountMax, outputBufferInt, context.SampleCount, WriteOffset, UpdateCount); + + // Convert the outputBuffer back to the target format of the renderer (float) + DataSourceHelper.ToFloat(outputBuffer, outputBufferInt, outputBuffer.Length); + + if (readResult != context.SampleCount) + { + outputBuffer.Slice((int)readResult, (int)context.SampleCount - (int)readResult).Fill(0); + } + } + else + { + AuxiliaryBufferInfo.Reset(context.MemoryManager, BufferInfo.SendBufferInfo); + AuxiliaryBufferInfo.Reset(context.MemoryManager, BufferInfo.ReturnBufferInfo); + + if (InputBufferIndex != OutputBufferIndex) + { + inputBuffer.CopyTo(outputBuffer); + } + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs new file mode 100644 index 00000000..b994c1cb --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs @@ -0,0 +1,51 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class BiquadFilterCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.BiquadFilter; + + public uint EstimatedProcessingTime { get; set; } + + public Memory BiquadFilterState { get; } + public int InputBufferIndex { get; } + public int OutputBufferIndex { get; } + public bool NeedInitialization { get; } + + private BiquadFilterParameter _parameter; + + public BiquadFilterCommand(int baseIndex, ref BiquadFilterParameter filter, Memory biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, bool needInitialization, int nodeId) + { + _parameter = filter; + BiquadFilterState = biquadFilterStateMemory; + InputBufferIndex = baseIndex + inputBufferOffset; + OutputBufferIndex = baseIndex + outputBufferOffset; + NeedInitialization = needInitialization; + + Enabled = true; + NodeId = nodeId; + } + + public void Process(CommandList context) + { + ref BiquadFilterState state = ref BiquadFilterState.Span[0]; + + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + if (NeedInitialization) + { + state = new BiquadFilterState(); + } + + BiquadFilterHelper.ProcessBiquadFilter(ref _parameter, ref state, outputBuffer, inputBuffer, context.SampleCount); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CaptureBufferCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CaptureBufferCommand.cs new file mode 100644 index 00000000..da1cb254 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CaptureBufferCommand.cs @@ -0,0 +1,136 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Renderer.Dsp.State.AuxiliaryBufferHeader; +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class CaptureBufferCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.CaptureBuffer; + + public uint EstimatedProcessingTime { get; set; } + + public uint InputBufferIndex { get; } + + public ulong CpuBufferInfoAddress { get; } + public ulong DspBufferInfoAddress { get; } + + public CpuAddress OutputBuffer { get; } + public uint CountMax { get; } + public uint UpdateCount { get; } + public uint WriteOffset { get; } + + public bool IsEffectEnabled { get; } + + public CaptureBufferCommand(uint bufferOffset, byte inputBufferOffset, ulong sendBufferInfo, bool isEnabled, + uint countMax, CpuAddress outputBuffer, uint updateCount, uint writeOffset, int nodeId) + { + Enabled = true; + NodeId = nodeId; + InputBufferIndex = bufferOffset + inputBufferOffset; + CpuBufferInfoAddress = sendBufferInfo; + DspBufferInfoAddress = sendBufferInfo + (ulong)Unsafe.SizeOf(); + OutputBuffer = outputBuffer; + CountMax = countMax; + UpdateCount = updateCount; + WriteOffset = writeOffset; + IsEffectEnabled = isEnabled; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private uint Write(IVirtualMemoryManager memoryManager, ulong outBufferAddress, uint countMax, ReadOnlySpan buffer, uint count, uint writeOffset, uint updateCount) + { + if (countMax == 0 || outBufferAddress == 0) + { + return 0; + } + + uint targetWriteOffset = writeOffset + AuxiliaryBufferInfo.GetWriteOffset(memoryManager, DspBufferInfoAddress); + + if (targetWriteOffset > countMax) + { + return 0; + } + + uint remaining = count; + + uint inBufferOffset = 0; + + while (remaining != 0) + { + uint countToWrite = Math.Min(countMax - targetWriteOffset, remaining); + + memoryManager.Write(outBufferAddress + targetWriteOffset * sizeof(int), MemoryMarshal.Cast(buffer.Slice((int)inBufferOffset, (int)countToWrite))); + + targetWriteOffset = (targetWriteOffset + countToWrite) % countMax; + remaining -= countToWrite; + inBufferOffset += countToWrite; + } + + if (updateCount != 0) + { + uint dspTotalSampleCount = AuxiliaryBufferInfo.GetTotalSampleCount(memoryManager, DspBufferInfoAddress); + uint cpuTotalSampleCount = AuxiliaryBufferInfo.GetTotalSampleCount(memoryManager, CpuBufferInfoAddress); + + uint totalSampleCountDiff = dspTotalSampleCount - cpuTotalSampleCount; + + if (totalSampleCountDiff >= countMax) + { + uint dspLostSampleCount = AuxiliaryBufferInfo.GetLostSampleCount(memoryManager, DspBufferInfoAddress); + uint cpuLostSampleCount = AuxiliaryBufferInfo.GetLostSampleCount(memoryManager, CpuBufferInfoAddress); + + uint lostSampleCountDiff = dspLostSampleCount - cpuLostSampleCount; + uint newLostSampleCount = lostSampleCountDiff + updateCount; + + if (lostSampleCountDiff > newLostSampleCount) + { + newLostSampleCount = cpuLostSampleCount - 1; + } + + AuxiliaryBufferInfo.SetLostSampleCount(memoryManager, DspBufferInfoAddress, newLostSampleCount); + } + + uint newWriteOffset = (AuxiliaryBufferInfo.GetWriteOffset(memoryManager, DspBufferInfoAddress) + updateCount) % countMax; + + AuxiliaryBufferInfo.SetWriteOffset(memoryManager, DspBufferInfoAddress, newWriteOffset); + + uint newTotalSampleCount = totalSampleCountDiff + newWriteOffset; + + AuxiliaryBufferInfo.SetTotalSampleCount(memoryManager, DspBufferInfoAddress, newTotalSampleCount); + } + + return count; + } + + public void Process(CommandList context) + { + Span inputBuffer = context.GetBuffer((int)InputBufferIndex); + + if (IsEffectEnabled) + { + Span inputBufferInt = MemoryMarshal.Cast(inputBuffer); + + // Convert input data to the target format for user (int) + DataSourceHelper.ToInt(inputBufferInt, inputBuffer, inputBuffer.Length); + + // Send the input to the user + Write(context.MemoryManager, OutputBuffer, CountMax, inputBufferInt, context.SampleCount, WriteOffset, UpdateCount); + + // Convert back to float + DataSourceHelper.ToFloat(inputBuffer, inputBufferInt, inputBuffer.Length); + } + else + { + AuxiliaryBufferInfo.Reset(context.MemoryManager, DspBufferInfoAddress); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs new file mode 100644 index 00000000..e50637eb --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs @@ -0,0 +1,76 @@ +using Ryujinx.Audio.Renderer.Parameter.Sink; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class CircularBufferSinkCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.CircularBufferSink; + + public uint EstimatedProcessingTime { get; set; } + + public ushort[] Input { get; } + public uint InputCount { get; } + + public ulong CircularBuffer { get; } + public ulong CircularBufferSize { get; } + public ulong CurrentOffset { get; } + + public CircularBufferSinkCommand(uint bufferOffset, ref CircularBufferParameter parameter, ref AddressInfo circularBufferAddressInfo, uint currentOffset, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + Input = new ushort[Constants.ChannelCountMax]; + InputCount = parameter.InputCount; + + for (int i = 0; i < InputCount; i++) + { + Input[i] = (ushort)(bufferOffset + parameter.Input[i]); + } + + CircularBuffer = circularBufferAddressInfo.GetReference(true); + CircularBufferSize = parameter.BufferSize; + CurrentOffset = currentOffset; + + Debug.Assert(CircularBuffer != 0); + } + + public void Process(CommandList context) + { + const int targetChannelCount = 2; + + ulong currentOffset = CurrentOffset; + + if (CircularBufferSize > 0) + { + for (int i = 0; i < InputCount; i++) + { + unsafe + { + float* inputBuffer = (float*)context.GetBufferPointer(Input[i]); + + ulong targetOffset = CircularBuffer + currentOffset; + + for (int y = 0; y < context.SampleCount; y++) + { + context.MemoryManager.Write(targetOffset + (ulong)y * targetChannelCount, PcmHelper.Saturate(inputBuffer[y])); + } + + currentOffset += context.SampleCount * targetChannelCount; + + if (currentOffset >= CircularBufferSize) + { + currentOffset = 0; + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs new file mode 100644 index 00000000..9e653e80 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class ClearMixBufferCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.ClearMixBuffer; + + public uint EstimatedProcessingTime { get; set; } + + public ClearMixBufferCommand(int nodeId) + { + Enabled = true; + NodeId = nodeId; + } + + public void Process(CommandList context) + { + context.ClearBuffers(); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs new file mode 100644 index 00000000..2cbed9c2 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs @@ -0,0 +1,155 @@ +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Renderer.Server; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class CommandList : IDisposable + { + public ulong StartTime { get; private set; } + public ulong EndTime { get; private set; } + public uint SampleCount { get; } + public uint SampleRate { get; } + + public Memory Buffers { get; } + public uint BufferCount { get; } + + public List Commands { get; } + + public IVirtualMemoryManager MemoryManager { get; } + + public IHardwareDevice OutputDevice { get; private set; } + + private readonly int _sampleCount; + private readonly int _buffersEntryCount; + private readonly MemoryHandle _buffersMemoryHandle; + + public CommandList(AudioRenderSystem renderSystem) : this(renderSystem.MemoryManager, + renderSystem.GetMixBuffer(), + renderSystem.GetSampleCount(), + renderSystem.GetSampleRate(), + renderSystem.GetMixBufferCount(), + renderSystem.GetVoiceChannelCountMax()) + { + } + + public CommandList(IVirtualMemoryManager memoryManager, Memory mixBuffer, uint sampleCount, uint sampleRate, uint mixBufferCount, uint voiceChannelCountMax) + { + SampleCount = sampleCount; + _sampleCount = (int)SampleCount; + SampleRate = sampleRate; + BufferCount = mixBufferCount + voiceChannelCountMax; + Buffers = mixBuffer; + Commands = new List(); + MemoryManager = memoryManager; + + _buffersEntryCount = Buffers.Length; + _buffersMemoryHandle = Buffers.Pin(); + } + + public void AddCommand(ICommand command) + { + Commands.Add(command); + } + + public void AddCommand(T command) where T : unmanaged, ICommand + { + throw new NotImplementedException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe IntPtr GetBufferPointer(int index) + { + if (index >= 0 && index < _buffersEntryCount) + { + return (IntPtr)((float*)_buffersMemoryHandle.Pointer + index * _sampleCount); + } + + throw new ArgumentOutOfRangeException(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ClearBuffer(int index) + { + Unsafe.InitBlock((void*)GetBufferPointer(index), 0, SampleCount); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void ClearBuffers() + { + Unsafe.InitBlock(_buffersMemoryHandle.Pointer, 0, (uint)_buffersEntryCount * sizeof(float)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe void CopyBuffer(int outputBufferIndex, int inputBufferIndex) + { + Unsafe.CopyBlock((void*)GetBufferPointer(outputBufferIndex), (void*)GetBufferPointer(inputBufferIndex), SampleCount); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetBuffer(int index) + { + if (index < 0 || index >= _buffersEntryCount) + { + return Span.Empty; + } + + unsafe + { + return new Span((float*)_buffersMemoryHandle.Pointer + index * _sampleCount, _sampleCount); + } + } + + public ulong GetTimeElapsedSinceDspStartedProcessing() + { + return (ulong)PerformanceCounter.ElapsedNanoseconds - StartTime; + } + + public void Process(IHardwareDevice outputDevice) + { + OutputDevice = outputDevice; + + StartTime = (ulong)PerformanceCounter.ElapsedNanoseconds; + + foreach (ICommand command in Commands) + { + if (command.Enabled) + { + bool shouldMeter = command.ShouldMeter(); + + long startTime = 0; + + if (shouldMeter) + { + startTime = PerformanceCounter.ElapsedNanoseconds; + } + + command.Process(this); + + if (shouldMeter) + { + ulong effectiveElapsedTime = (ulong)(PerformanceCounter.ElapsedNanoseconds - startTime); + + if (effectiveElapsedTime > command.EstimatedProcessingTime) + { + Logger.Warning?.Print(LogClass.AudioRenderer, $"Command {command.GetType().Name} took {effectiveElapsedTime}ns (expected {command.EstimatedProcessingTime}ns)"); + } + } + } + } + + EndTime = (ulong)PerformanceCounter.ElapsedNanoseconds; + } + + public void Dispose() + { + _buffersMemoryHandle.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs new file mode 100644 index 00000000..9ce181b1 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs @@ -0,0 +1,37 @@ +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public enum CommandType : byte + { + Invalid, + PcmInt16DataSourceVersion1, + PcmInt16DataSourceVersion2, + PcmFloatDataSourceVersion1, + PcmFloatDataSourceVersion2, + AdpcmDataSourceVersion1, + AdpcmDataSourceVersion2, + Volume, + VolumeRamp, + BiquadFilter, + Mix, + MixRamp, + MixRampGrouped, + DepopPrepare, + DepopForMixBuffers, + Delay, + Upsample, + DownMixSurroundToStereo, + AuxiliaryBuffer, + DeviceSink, + CircularBufferSink, + Reverb, + Reverb3d, + Performance, + ClearMixBuffer, + CopyMixBuffer, + LimiterVersion1, + LimiterVersion2, + GroupedBiquadFilter, + CaptureBuffer, + Compressor + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs new file mode 100644 index 00000000..34231e61 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs @@ -0,0 +1,173 @@ +using Ryujinx.Audio.Renderer.Dsp.Effect; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class CompressorCommand : ICommand + { + private const int FixedPointPrecision = 15; + + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.Compressor; + + public uint EstimatedProcessingTime { get; set; } + + public CompressorParameter Parameter => _parameter; + public Memory State { get; } + public ushort[] OutputBufferIndices { get; } + public ushort[] InputBufferIndices { get; } + public bool IsEffectEnabled { get; } + + private CompressorParameter _parameter; + + public CompressorCommand(uint bufferOffset, CompressorParameter parameter, Memory state, bool isEnabled, int nodeId) + { + Enabled = true; + NodeId = nodeId; + _parameter = parameter; + State = state; + + IsEffectEnabled = isEnabled; + + InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + + for (int i = 0; i < _parameter.ChannelCount; i++) + { + InputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Input[i]); + OutputBufferIndices[i] = (ushort)(bufferOffset + _parameter.Output[i]); + } + } + + public void Process(CommandList context) + { + ref CompressorState state = ref State.Span[0]; + + if (IsEffectEnabled) + { + if (_parameter.Status == Server.Effect.UsageState.Invalid) + { + state = new CompressorState(ref _parameter); + } + else if (_parameter.Status == Server.Effect.UsageState.New) + { + state.UpdateParameter(ref _parameter); + } + } + + ProcessCompressor(context, ref state); + } + + private unsafe void ProcessCompressor(CommandList context, ref CompressorState state) + { + Debug.Assert(_parameter.IsChannelCountValid()); + + if (IsEffectEnabled && _parameter.IsChannelCountValid()) + { + Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span channelInput = stackalloc float[Parameter.ChannelCount]; + ExponentialMovingAverage inputMovingAverage = state.InputMovingAverage; + float unknown4 = state.Unknown4; + ExponentialMovingAverage compressionGainAverage = state.CompressionGainAverage; + float previousCompressionEmaAlpha = state.PreviousCompressionEmaAlpha; + + for (int i = 0; i < _parameter.ChannelCount; i++) + { + inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]); + outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]); + } + + for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++) + { + for (int channelIndex = 0; channelIndex < _parameter.ChannelCount; channelIndex++) + { + channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex); + } + + float newMean = inputMovingAverage.Update(FloatingPointHelper.MeanSquare(channelInput), _parameter.InputGain); + float y = FloatingPointHelper.Log10(newMean) * 10.0f; + float z = 0.0f; + + bool unknown10OutOfRange = false; + + if (newMean < 1.0e-10f) + { + z = 1.0f; + + unknown10OutOfRange = state.Unknown10 < -100.0f; + } + + if (y >= state.Unknown10 || unknown10OutOfRange) + { + float tmpGain; + + if (y >= state.Unknown14) + { + tmpGain = ((1.0f / Parameter.Ratio) - 1.0f) * (y - Parameter.Threshold); + } + else + { + tmpGain = (y - state.Unknown10) * ((y - state.Unknown10) * -state.CompressorGainReduction); + } + + z = FloatingPointHelper.DecibelToLinearExtended(tmpGain); + } + + float unknown4New = z; + float compressionEmaAlpha; + + if ((unknown4 - z) <= 0.08f) + { + compressionEmaAlpha = Parameter.ReleaseCoefficient; + + if ((unknown4 - z) >= -0.08f) + { + if (MathF.Abs(compressionGainAverage.Read() - z) >= 0.001f) + { + unknown4New = unknown4; + } + + compressionEmaAlpha = previousCompressionEmaAlpha; + } + } + else + { + compressionEmaAlpha = Parameter.AttackCoefficient; + } + + float compressionGain = compressionGainAverage.Update(z, compressionEmaAlpha); + + for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++) + { + *((float*)outputBuffers[channelIndex] + sampleIndex) = channelInput[channelIndex] * compressionGain * state.OutputGain; + } + + unknown4 = unknown4New; + previousCompressionEmaAlpha = compressionEmaAlpha; + } + + state.InputMovingAverage = inputMovingAverage; + state.Unknown4 = unknown4; + state.CompressionGainAverage = compressionGainAverage; + state.PreviousCompressionEmaAlpha = previousCompressionEmaAlpha; + } + else + { + for (int i = 0; i < Parameter.ChannelCount; i++) + { + if (InputBufferIndices[i] != OutputBufferIndices[i]) + { + context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]); + } + } + } + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs new file mode 100644 index 00000000..7237fddf --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs @@ -0,0 +1,30 @@ +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class CopyMixBufferCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.CopyMixBuffer; + + public uint EstimatedProcessingTime { get; set; } + + public ushort InputBufferIndex { get; } + public ushort OutputBufferIndex { get; } + + public CopyMixBufferCommand(uint inputBufferIndex, uint outputBufferIndex, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndex = (ushort)inputBufferIndex; + OutputBufferIndex = (ushort)outputBufferIndex; + } + + public void Process(CommandList context) + { + context.CopyBuffer(OutputBufferIndex, InputBufferIndex); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs new file mode 100644 index 00000000..c1503b6a --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs @@ -0,0 +1,108 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using System; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class DataSourceVersion2Command : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType { get; } + + public uint EstimatedProcessingTime { get; set; } + + public ushort OutputBufferIndex { get; } + public uint SampleRate { get; } + + public float Pitch { get; } + + public WaveBuffer[] WaveBuffers { get; } + + public Memory State { get; } + + public ulong ExtraParameter { get; } + public ulong ExtraParameterSize { get; } + + public uint ChannelIndex { get; } + + public uint ChannelCount { get; } + + public DecodingBehaviour DecodingBehaviour { get; } + + public SampleFormat SampleFormat { get; } + + public SampleRateConversionQuality SrcQuality { get; } + + public DataSourceVersion2Command(ref Server.Voice.VoiceState serverState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + Enabled = true; + NodeId = nodeId; + ChannelIndex = channelIndex; + ChannelCount = serverState.ChannelsCount; + SampleFormat = serverState.SampleFormat; + SrcQuality = serverState.SrcQuality; + CommandType = GetCommandTypeBySampleFormat(SampleFormat); + + OutputBufferIndex = (ushort)(channelIndex + outputBufferIndex); + SampleRate = serverState.SampleRate; + Pitch = serverState.Pitch; + + WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount]; + + for (int i = 0; i < WaveBuffers.Length; i++) + { + ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i]; + + WaveBuffers[i] = voiceWaveBuffer.ToCommon(2); + } + + if (SampleFormat == SampleFormat.Adpcm) + { + ExtraParameter = serverState.DataSourceStateAddressInfo.GetReference(true); + ExtraParameterSize = serverState.DataSourceStateAddressInfo.Size; + } + + State = state; + DecodingBehaviour = serverState.DecodingBehaviour; + } + + private static CommandType GetCommandTypeBySampleFormat(SampleFormat sampleFormat) + { + switch (sampleFormat) + { + case SampleFormat.Adpcm: + return CommandType.AdpcmDataSourceVersion2; + case SampleFormat.PcmInt16: + return CommandType.PcmInt16DataSourceVersion2; + case SampleFormat.PcmFloat: + return CommandType.PcmFloatDataSourceVersion2; + default: + throw new NotImplementedException($"{sampleFormat}"); + } + } + + public void Process(CommandList context) + { + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + DataSourceHelper.WaveBufferInformation info = new DataSourceHelper.WaveBufferInformation + { + SourceSampleRate = SampleRate, + SampleFormat = SampleFormat, + Pitch = Pitch, + DecodingBehaviour = DecodingBehaviour, + ExtraParameter = ExtraParameter, + ExtraParameterSize = ExtraParameterSize, + ChannelIndex = (int)ChannelIndex, + ChannelCount = (int)ChannelCount, + SrcQuality = SrcQuality + }; + + DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, ref info, WaveBuffers, ref State.Span[0], context.SampleRate, (int)context.SampleCount); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs new file mode 100644 index 00000000..cb5678c7 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs @@ -0,0 +1,280 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Audio.Renderer.Utils.Math; +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class DelayCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.Delay; + + public uint EstimatedProcessingTime { get; set; } + + public DelayParameter Parameter => _parameter; + public Memory State { get; } + public ulong WorkBuffer { get; } + public ushort[] OutputBufferIndices { get; } + public ushort[] InputBufferIndices { get; } + public bool IsEffectEnabled { get; } + + private DelayParameter _parameter; + + private const int FixedPointPrecision = 14; + + public DelayCommand(uint bufferOffset, DelayParameter parameter, Memory state, bool isEnabled, ulong workBuffer, int nodeId, bool newEffectChannelMappingSupported) + { + Enabled = true; + NodeId = nodeId; + _parameter = parameter; + State = state; + WorkBuffer = workBuffer; + + IsEffectEnabled = isEnabled; + + InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]); + OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); + } + + DataSourceHelper.RemapLegacyChannelEffectMappingToChannelResourceMapping(newEffectChannelMappingSupported, InputBufferIndices); + DataSourceHelper.RemapLegacyChannelEffectMappingToChannelResourceMapping(newEffectChannelMappingSupported, OutputBufferIndices); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private unsafe void ProcessDelayMono(ref DelayState state, float* outputBuffer, float* inputBuffer, uint sampleCount) + { + const ushort channelCount = 1; + + float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision); + float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision); + float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision); + float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision); + + for (int i = 0; i < sampleCount; i++) + { + float input = inputBuffer[i] * 64; + float delayLineValue = state.DelayLines[0].Read(); + + float temp = input * inGain + delayLineValue * feedbackGain; + + state.UpdateLowPassFilter(ref temp, channelCount); + + outputBuffer[i] = (input * dryGain + delayLineValue * outGain) / 64; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private unsafe void ProcessDelayStereo(ref DelayState state, Span outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + const ushort channelCount = 2; + + float delayFeedbackBaseGain = state.DelayFeedbackBaseGain; + float delayFeedbackCrossGain = state.DelayFeedbackCrossGain; + float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision); + float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision); + float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision); + + Matrix2x2 delayFeedback = new Matrix2x2(delayFeedbackBaseGain, delayFeedbackCrossGain, + delayFeedbackCrossGain, delayFeedbackBaseGain); + + for (int i = 0; i < sampleCount; i++) + { + Vector2 channelInput = new Vector2 + { + X = *((float*)inputBuffers[0] + i) * 64, + Y = *((float*)inputBuffers[1] + i) * 64, + }; + + Vector2 delayLineValues = new Vector2() + { + X = state.DelayLines[0].Read(), + Y = state.DelayLines[1].Read(), + }; + + Vector2 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain; + + state.UpdateLowPassFilter(ref Unsafe.As(ref temp), channelCount); + + *((float*)outputBuffers[0] + i) = (channelInput.X * dryGain + delayLineValues.X * outGain) / 64; + *((float*)outputBuffers[1] + i) = (channelInput.Y * dryGain + delayLineValues.Y * outGain) / 64; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private unsafe void ProcessDelayQuadraphonic(ref DelayState state, Span outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + const ushort channelCount = 4; + + float delayFeedbackBaseGain = state.DelayFeedbackBaseGain; + float delayFeedbackCrossGain = state.DelayFeedbackCrossGain; + float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision); + float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision); + float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision); + + Matrix4x4 delayFeedback = new Matrix4x4(delayFeedbackBaseGain, delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f, + delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, + delayFeedbackCrossGain, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, + 0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain); + + + for (int i = 0; i < sampleCount; i++) + { + Vector4 channelInput = new Vector4 + { + X = *((float*)inputBuffers[0] + i) * 64, + Y = *((float*)inputBuffers[1] + i) * 64, + Z = *((float*)inputBuffers[2] + i) * 64, + W = *((float*)inputBuffers[3] + i) * 64 + }; + + Vector4 delayLineValues = new Vector4() + { + X = state.DelayLines[0].Read(), + Y = state.DelayLines[1].Read(), + Z = state.DelayLines[2].Read(), + W = state.DelayLines[3].Read() + }; + + Vector4 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain; + + state.UpdateLowPassFilter(ref Unsafe.As(ref temp), channelCount); + + *((float*)outputBuffers[0] + i) = (channelInput.X * dryGain + delayLineValues.X * outGain) / 64; + *((float*)outputBuffers[1] + i) = (channelInput.Y * dryGain + delayLineValues.Y * outGain) / 64; + *((float*)outputBuffers[2] + i) = (channelInput.Z * dryGain + delayLineValues.Z * outGain) / 64; + *((float*)outputBuffers[3] + i) = (channelInput.W * dryGain + delayLineValues.W * outGain) / 64; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private unsafe void ProcessDelaySurround(ref DelayState state, Span outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + const ushort channelCount = 6; + + float feedbackGain = FixedPointHelper.ToFloat(Parameter.FeedbackGain, FixedPointPrecision); + float delayFeedbackBaseGain = state.DelayFeedbackBaseGain; + float delayFeedbackCrossGain = state.DelayFeedbackCrossGain; + float inGain = FixedPointHelper.ToFloat(Parameter.InGain, FixedPointPrecision); + float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision); + float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision); + + Matrix6x6 delayFeedback = new Matrix6x6(delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, 0.0f, delayFeedbackCrossGain, 0.0f, + 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, + delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, feedbackGain, 0.0f, 0.0f, + delayFeedbackCrossGain, 0.0f, 0.0f, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, + 0.0f, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackBaseGain); + + for (int i = 0; i < sampleCount; i++) + { + Vector6 channelInput = new Vector6 + { + X = *((float*)inputBuffers[0] + i) * 64, + Y = *((float*)inputBuffers[1] + i) * 64, + Z = *((float*)inputBuffers[2] + i) * 64, + W = *((float*)inputBuffers[3] + i) * 64, + V = *((float*)inputBuffers[4] + i) * 64, + U = *((float*)inputBuffers[5] + i) * 64 + }; + + Vector6 delayLineValues = new Vector6 + { + X = state.DelayLines[0].Read(), + Y = state.DelayLines[1].Read(), + Z = state.DelayLines[2].Read(), + W = state.DelayLines[3].Read(), + V = state.DelayLines[4].Read(), + U = state.DelayLines[5].Read() + }; + + Vector6 temp = MatrixHelper.Transform(ref delayLineValues, ref delayFeedback) + channelInput * inGain; + + state.UpdateLowPassFilter(ref Unsafe.As(ref temp), channelCount); + + *((float*)outputBuffers[0] + i) = (channelInput.X * dryGain + delayLineValues.X * outGain) / 64; + *((float*)outputBuffers[1] + i) = (channelInput.Y * dryGain + delayLineValues.Y * outGain) / 64; + *((float*)outputBuffers[2] + i) = (channelInput.Z * dryGain + delayLineValues.Z * outGain) / 64; + *((float*)outputBuffers[3] + i) = (channelInput.W * dryGain + delayLineValues.W * outGain) / 64; + *((float*)outputBuffers[4] + i) = (channelInput.V * dryGain + delayLineValues.V * outGain) / 64; + *((float*)outputBuffers[5] + i) = (channelInput.U * dryGain + delayLineValues.U * outGain) / 64; + } + } + + private unsafe void ProcessDelay(CommandList context, ref DelayState state) + { + Debug.Assert(Parameter.IsChannelCountValid()); + + if (IsEffectEnabled && Parameter.IsChannelCountValid()) + { + Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]); + outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]); + } + + switch (Parameter.ChannelCount) + { + case 1: + ProcessDelayMono(ref state, (float*)outputBuffers[0], (float*)inputBuffers[0], context.SampleCount); + break; + case 2: + ProcessDelayStereo(ref state, outputBuffers, inputBuffers, context.SampleCount); + break; + case 4: + ProcessDelayQuadraphonic(ref state, outputBuffers, inputBuffers, context.SampleCount); + break; + case 6: + ProcessDelaySurround(ref state, outputBuffers, inputBuffers, context.SampleCount); + break; + default: + throw new NotImplementedException(Parameter.ChannelCount.ToString()); + } + } + else + { + for (int i = 0; i < Parameter.ChannelCount; i++) + { + if (InputBufferIndices[i] != OutputBufferIndices[i]) + { + context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]); + } + } + } + } + + public void Process(CommandList context) + { + ref DelayState state = ref State.Span[0]; + + if (IsEffectEnabled) + { + if (Parameter.Status == UsageState.Invalid) + { + state = new DelayState(ref _parameter, WorkBuffer); + } + else if (Parameter.Status == UsageState.New) + { + state.UpdateParameter(ref _parameter); + } + } + + ProcessDelay(context, ref state); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs new file mode 100644 index 00000000..1dba56e6 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs @@ -0,0 +1,92 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class DepopForMixBuffersCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.DepopForMixBuffers; + + public uint EstimatedProcessingTime { get; set; } + + public uint MixBufferOffset { get; } + + public uint MixBufferCount { get; } + + public float Decay { get; } + + public Memory DepopBuffer { get; } + + public DepopForMixBuffersCommand(Memory depopBuffer, uint bufferOffset, uint mixBufferCount, int nodeId, uint sampleRate) + { + Enabled = true; + NodeId = nodeId; + MixBufferOffset = bufferOffset; + MixBufferCount = mixBufferCount; + DepopBuffer = depopBuffer; + + if (sampleRate == 48000) + { + Decay = 0.962189f; + } + else // if (sampleRate == 32000) + { + Decay = 0.943695f; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe float ProcessDepopMix(float* buffer, float depopValue, uint sampleCount) + { + if (depopValue < 0) + { + depopValue = -depopValue; + + for (int i = 0; i < sampleCount; i++) + { + depopValue = FloatingPointHelper.MultiplyRoundDown(Decay, depopValue); + + buffer[i] -= depopValue; + } + + return -depopValue; + } + else + { + for (int i = 0; i < sampleCount; i++) + { + depopValue = FloatingPointHelper.MultiplyRoundDown(Decay, depopValue); + + buffer[i] += depopValue; + } + + return depopValue; + } + } + + public void Process(CommandList context) + { + Span depopBuffer = DepopBuffer.Span; + + uint bufferCount = Math.Min(MixBufferOffset + MixBufferCount, context.BufferCount); + + for (int i = (int)MixBufferOffset; i < bufferCount; i++) + { + float depopValue = depopBuffer[i]; + if (depopValue != 0) + { + unsafe + { + float* buffer = (float*)context.GetBufferPointer(i); + + depopBuffer[i] = ProcessDepopMix(buffer, depopValue, context.SampleCount); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs new file mode 100644 index 00000000..d02f7c12 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs @@ -0,0 +1,57 @@ +using Ryujinx.Audio.Renderer.Common; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class DepopPrepareCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.DepopPrepare; + + public uint EstimatedProcessingTime { get; set; } + + public uint MixBufferCount { get; } + + public ushort[] OutputBufferIndices { get; } + + public Memory State { get; } + public Memory DepopBuffer { get; } + + public DepopPrepareCommand(Memory state, Memory depopBuffer, uint mixBufferCount, uint bufferOffset, int nodeId, bool enabled) + { + Enabled = enabled; + NodeId = nodeId; + MixBufferCount = mixBufferCount; + + OutputBufferIndices = new ushort[Constants.MixBufferCountMax]; + + for (int i = 0; i < Constants.MixBufferCountMax; i++) + { + OutputBufferIndices[i] = (ushort)(bufferOffset + i); + } + + State = state; + DepopBuffer = depopBuffer; + } + + public void Process(CommandList context) + { + ref VoiceUpdateState state = ref State.Span[0]; + + Span depopBuffer = DepopBuffer.Span; + + for (int i = 0; i < MixBufferCount; i++) + { + if (state.LastSamples[i] != 0) + { + depopBuffer[OutputBufferIndices[i]] += state.LastSamples[i]; + + state.LastSamples[i] = 0; + } + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs new file mode 100644 index 00000000..9c88a4e7 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs @@ -0,0 +1,91 @@ +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Renderer.Server.Sink; +using System; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class DeviceSinkCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.DeviceSink; + + public uint EstimatedProcessingTime { get; set; } + + public string DeviceName { get; } + + public int SessionId { get; } + + public uint InputCount { get; } + public ushort[] InputBufferIndices { get; } + + public Memory Buffers { get; } + + public DeviceSinkCommand(uint bufferOffset, DeviceSink sink, int sessionId, Memory buffers, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + DeviceName = Encoding.ASCII.GetString(sink.Parameter.DeviceName).TrimEnd('\0'); + SessionId = sessionId; + InputCount = sink.Parameter.InputCount; + InputBufferIndices = new ushort[InputCount]; + + for (int i = 0; i < Math.Min(InputCount, Constants.ChannelCountMax); i++) + { + InputBufferIndices[i] = (ushort)(bufferOffset + sink.Parameter.Input[i]); + } + + if (sink.UpsamplerState != null) + { + Buffers = sink.UpsamplerState.OutputBuffer; + } + else + { + Buffers = buffers; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Span GetBuffer(int index, int sampleCount) + { + return Buffers.Span.Slice(index * sampleCount, sampleCount); + } + + public void Process(CommandList context) + { + IHardwareDevice device = context.OutputDevice; + + if (device.GetSampleRate() == Constants.TargetSampleRate) + { + int channelCount = (int)device.GetChannelCount(); + uint bufferCount = Math.Min(device.GetChannelCount(), InputCount); + + const int sampleCount = Constants.TargetSampleCount; + + short[] outputBuffer = new short[bufferCount * sampleCount]; + + for (int i = 0; i < bufferCount; i++) + { + ReadOnlySpan inputBuffer = GetBuffer(InputBufferIndices[i], sampleCount); + + for (int j = 0; j < sampleCount; j++) + { + outputBuffer[i + j * channelCount] = PcmHelper.Saturate(inputBuffer[j]); + } + } + + device.AppendBuffer(outputBuffer, InputCount); + } + else + { + // TODO: support resampling for device only supporting something different + throw new NotImplementedException(); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs new file mode 100644 index 00000000..79cefcc5 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs @@ -0,0 +1,68 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class DownMixSurroundToStereoCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.DownMixSurroundToStereo; + + public uint EstimatedProcessingTime { get; set; } + + public ushort[] InputBufferIndices { get; } + public ushort[] OutputBufferIndices { get; } + + public float[] Coefficients { get; } + + public DownMixSurroundToStereoCommand(uint bufferOffset, Span inputBufferOffset, Span outputBufferOffset, float[] downMixParameter, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + + for (int i = 0; i < Constants.VoiceChannelCountMax; i++) + { + InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]); + OutputBufferIndices[i] = (ushort)(bufferOffset + outputBufferOffset[i]); + } + + Coefficients = downMixParameter; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float DownMixSurroundToStereo(ReadOnlySpan coefficients, float back, float lfe, float center, float front) + { + return FloatingPointHelper.RoundUp(coefficients[3] * back + coefficients[2] * lfe + coefficients[1] * center + coefficients[0] * front); + } + + public void Process(CommandList context) + { + ReadOnlySpan frontLeft = context.GetBuffer(InputBufferIndices[0]); + ReadOnlySpan frontRight = context.GetBuffer(InputBufferIndices[1]); + ReadOnlySpan frontCenter = context.GetBuffer(InputBufferIndices[2]); + ReadOnlySpan lowFrequency = context.GetBuffer(InputBufferIndices[3]); + ReadOnlySpan backLeft = context.GetBuffer(InputBufferIndices[4]); + ReadOnlySpan backRight = context.GetBuffer(InputBufferIndices[5]); + + Span stereoLeft = context.GetBuffer(OutputBufferIndices[0]); + Span stereoRight = context.GetBuffer(OutputBufferIndices[1]); + + for (int i = 0; i < context.SampleCount; i++) + { + stereoLeft[i] = DownMixSurroundToStereo(Coefficients, backLeft[i], lowFrequency[i], frontCenter[i], frontLeft[i]); + stereoRight[i] = DownMixSurroundToStereo(Coefficients, backRight[i], lowFrequency[i], frontCenter[i], frontRight[i]); + } + + context.ClearBuffer(OutputBufferIndices[2]); + context.ClearBuffer(OutputBufferIndices[3]); + context.ClearBuffer(OutputBufferIndices[4]); + context.ClearBuffer(OutputBufferIndices[5]); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs new file mode 100644 index 00000000..b190cc10 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/GroupedBiquadFilterCommand.cs @@ -0,0 +1,62 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class GroupedBiquadFilterCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.GroupedBiquadFilter; + + public uint EstimatedProcessingTime { get; set; } + + private BiquadFilterParameter[] _parameters; + private Memory _biquadFilterStates; + private int _inputBufferIndex; + private int _outputBufferIndex; + private bool[] _isInitialized; + + public GroupedBiquadFilterCommand(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId) + { + _parameters = filters.ToArray(); + _biquadFilterStates = biquadFilterStateMemory; + _inputBufferIndex = baseIndex + inputBufferOffset; + _outputBufferIndex = baseIndex + outputBufferOffset; + _isInitialized = isInitialized.ToArray(); + + Enabled = true; + NodeId = nodeId; + } + + public void Process(CommandList context) + { + Span states = _biquadFilterStates.Span; + + ReadOnlySpan inputBuffer = context.GetBuffer(_inputBufferIndex); + Span outputBuffer = context.GetBuffer(_outputBufferIndex); + + for (int i = 0; i < _parameters.Length; i++) + { + if (!_isInitialized[i]) + { + states[i] = new BiquadFilterState(); + } + } + + // NOTE: Nintendo only implement single and double biquad filters but no generic path when the command definition suggests it could be done. + // As such we currently only implement a generic path for simplicity for double biquad. + if (_parameters.Length == 1) + { + BiquadFilterHelper.ProcessBiquadFilter(ref _parameters[0], ref states[0], outputBuffer, inputBuffer, context.SampleCount); + } + else + { + BiquadFilterHelper.ProcessBiquadFilter(_parameters, states, outputBuffer, inputBuffer, context.SampleCount); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs new file mode 100644 index 00000000..d281e6e9 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public interface ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType { get; } + + public uint EstimatedProcessingTime { get; } + + public void Process(CommandList context); + + public bool ShouldMeter() + { + return false; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs new file mode 100644 index 00000000..a464ad70 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion1.cs @@ -0,0 +1,144 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class LimiterCommandVersion1 : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.LimiterVersion1; + + public uint EstimatedProcessingTime { get; set; } + + public LimiterParameter Parameter => _parameter; + public Memory State { get; } + public ulong WorkBuffer { get; } + public ushort[] OutputBufferIndices { get; } + public ushort[] InputBufferIndices { get; } + public bool IsEffectEnabled { get; } + + private LimiterParameter _parameter; + + public LimiterCommandVersion1(uint bufferOffset, LimiterParameter parameter, Memory state, bool isEnabled, ulong workBuffer, int nodeId) + { + Enabled = true; + NodeId = nodeId; + _parameter = parameter; + State = state; + WorkBuffer = workBuffer; + + IsEffectEnabled = isEnabled; + + InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]); + OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); + } + } + + public void Process(CommandList context) + { + ref LimiterState state = ref State.Span[0]; + + if (IsEffectEnabled) + { + if (Parameter.Status == Server.Effect.UsageState.Invalid) + { + state = new LimiterState(ref _parameter, WorkBuffer); + } + else if (Parameter.Status == Server.Effect.UsageState.New) + { + state.UpdateParameter(ref _parameter); + } + } + + ProcessLimiter(context, ref state); + } + + private unsafe void ProcessLimiter(CommandList context, ref LimiterState state) + { + Debug.Assert(Parameter.IsChannelCountValid()); + + if (IsEffectEnabled && Parameter.IsChannelCountValid()) + { + Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]); + outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]); + } + + for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++) + { + for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++) + { + float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex); + + float inputSample = (rawInputSample / short.MaxValue) * Parameter.InputGain; + + float sampleInputMax = Math.Abs(inputSample); + + float inputCoefficient = Parameter.ReleaseCoefficient; + + if (sampleInputMax > state.DetectorAverage[channelIndex].Read()) + { + inputCoefficient = Parameter.AttackCoefficient; + } + + float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient); + float attenuation = 1.0f; + + if (detectorValue > Parameter.Threshold) + { + attenuation = Parameter.Threshold / detectorValue; + } + + float outputCoefficient = Parameter.ReleaseCoefficient; + + if (state.CompressionGainAverage[channelIndex].Read() > attenuation) + { + outputCoefficient = Parameter.AttackCoefficient; + } + + float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient); + + ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]]; + + float outputSample = delayedSample * compressionGain * Parameter.OutputGain; + + *((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue; + + delayedSample = inputSample; + + state.DelayedSampleBufferPosition[channelIndex]++; + + while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin) + { + state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin; + } + } + } + } + else + { + for (int i = 0; i < Parameter.ChannelCount; i++) + { + if (InputBufferIndices[i] != OutputBufferIndices[i]) + { + context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs new file mode 100644 index 00000000..950de97b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs @@ -0,0 +1,163 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class LimiterCommandVersion2 : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.LimiterVersion2; + + public uint EstimatedProcessingTime { get; set; } + + public LimiterParameter Parameter => _parameter; + public Memory State { get; } + public Memory ResultState { get; } + public ulong WorkBuffer { get; } + public ushort[] OutputBufferIndices { get; } + public ushort[] InputBufferIndices { get; } + public bool IsEffectEnabled { get; } + + private LimiterParameter _parameter; + + public LimiterCommandVersion2(uint bufferOffset, LimiterParameter parameter, Memory state, Memory resultState, bool isEnabled, ulong workBuffer, int nodeId) + { + Enabled = true; + NodeId = nodeId; + _parameter = parameter; + State = state; + ResultState = resultState; + WorkBuffer = workBuffer; + + IsEffectEnabled = isEnabled; + + InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]); + OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); + } + } + + public void Process(CommandList context) + { + ref LimiterState state = ref State.Span[0]; + + if (IsEffectEnabled) + { + if (Parameter.Status == Server.Effect.UsageState.Invalid) + { + state = new LimiterState(ref _parameter, WorkBuffer); + } + else if (Parameter.Status == Server.Effect.UsageState.New) + { + state.UpdateParameter(ref _parameter); + } + } + + ProcessLimiter(context, ref state); + } + + private unsafe void ProcessLimiter(CommandList context, ref LimiterState state) + { + Debug.Assert(Parameter.IsChannelCountValid()); + + if (IsEffectEnabled && Parameter.IsChannelCountValid()) + { + if (!ResultState.IsEmpty && Parameter.StatisticsReset) + { + ref LimiterStatistics statistics = ref MemoryMarshal.Cast(ResultState.Span[0].SpecificData)[0]; + + statistics.Reset(); + } + + Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]); + outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]); + } + + for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++) + { + for (int sampleIndex = 0; sampleIndex < context.SampleCount; sampleIndex++) + { + float rawInputSample = *((float*)inputBuffers[channelIndex] + sampleIndex); + + float inputSample = (rawInputSample / short.MaxValue) * Parameter.InputGain; + + float sampleInputMax = Math.Abs(inputSample); + + float inputCoefficient = Parameter.ReleaseCoefficient; + + if (sampleInputMax > state.DetectorAverage[channelIndex].Read()) + { + inputCoefficient = Parameter.AttackCoefficient; + } + + float detectorValue = state.DetectorAverage[channelIndex].Update(sampleInputMax, inputCoefficient); + float attenuation = 1.0f; + + if (detectorValue > Parameter.Threshold) + { + attenuation = Parameter.Threshold / detectorValue; + } + + float outputCoefficient = Parameter.ReleaseCoefficient; + + if (state.CompressionGainAverage[channelIndex].Read() > attenuation) + { + outputCoefficient = Parameter.AttackCoefficient; + } + + float compressionGain = state.CompressionGainAverage[channelIndex].Update(attenuation, outputCoefficient); + + ref float delayedSample = ref state.DelayedSampleBuffer[channelIndex * Parameter.DelayBufferSampleCountMax + state.DelayedSampleBufferPosition[channelIndex]]; + + float outputSample = delayedSample * compressionGain * Parameter.OutputGain; + + *((float*)outputBuffers[channelIndex] + sampleIndex) = outputSample * short.MaxValue; + + delayedSample = inputSample; + + state.DelayedSampleBufferPosition[channelIndex]++; + + while (state.DelayedSampleBufferPosition[channelIndex] >= Parameter.DelayBufferSampleCountMin) + { + state.DelayedSampleBufferPosition[channelIndex] -= Parameter.DelayBufferSampleCountMin; + } + + if (!ResultState.IsEmpty) + { + ref LimiterStatistics statistics = ref MemoryMarshal.Cast(ResultState.Span[0].SpecificData)[0]; + + statistics.InputMax[channelIndex] = Math.Max(statistics.InputMax[channelIndex], sampleInputMax); + statistics.CompressionGainMin[channelIndex] = Math.Min(statistics.CompressionGainMin[channelIndex], compressionGain); + } + } + } + } + else + { + for (int i = 0; i < Parameter.ChannelCount; i++) + { + if (InputBufferIndices[i] != OutputBufferIndices[i]) + { + context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]); + } + } + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs new file mode 100644 index 00000000..2616bda5 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs @@ -0,0 +1,137 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Intrinsics.X86; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class MixCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.Mix; + + public uint EstimatedProcessingTime { get; set; } + + public ushort InputBufferIndex { get; } + public ushort OutputBufferIndex { get; } + + public float Volume { get; } + + public MixCommand(uint inputBufferIndex, uint outputBufferIndex, int nodeId, float volume) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndex = (ushort)inputBufferIndex; + OutputBufferIndex = (ushort)outputBufferIndex; + + Volume = volume; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessMixAvx(Span outputMix, ReadOnlySpan inputMix) + { + Vector256 volumeVec = Vector256.Create(Volume); + + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputMix); + Span> outputVec = MemoryMarshal.Cast>(outputMix); + + int sisdStart = inputVec.Length * 8; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Avx.Add(outputVec[i], Avx.Ceiling(Avx.Multiply(inputVec[i], volumeVec))); + } + + for (int i = sisdStart; i < inputMix.Length; i++) + { + outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessMixSse41(Span outputMix, ReadOnlySpan inputMix) + { + Vector128 volumeVec = Vector128.Create(Volume); + + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputMix); + Span> outputVec = MemoryMarshal.Cast>(outputMix); + + int sisdStart = inputVec.Length * 4; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Sse.Add(outputVec[i], Sse41.Ceiling(Sse.Multiply(inputVec[i], volumeVec))); + } + + for (int i = sisdStart; i < inputMix.Length; i++) + { + outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessMixAdvSimd(Span outputMix, ReadOnlySpan inputMix) + { + Vector128 volumeVec = Vector128.Create(Volume); + + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputMix); + Span> outputVec = MemoryMarshal.Cast>(outputMix); + + int sisdStart = inputVec.Length * 4; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = AdvSimd.Add(outputVec[i], AdvSimd.Ceiling(AdvSimd.Multiply(inputVec[i], volumeVec))); + } + + for (int i = sisdStart; i < inputMix.Length; i++) + { + outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessMixSlowPath(Span outputMix, ReadOnlySpan inputMix) + { + for (int i = 0; i < inputMix.Length; i++) + { + outputMix[i] += FloatingPointHelper.MultiplyRoundUp(inputMix[i], Volume); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessMix(Span outputMix, ReadOnlySpan inputMix) + { + if (Avx.IsSupported) + { + ProcessMixAvx(outputMix, inputMix); + } + else if (Sse41.IsSupported) + { + ProcessMixSse41(outputMix, inputMix); + } + else if (AdvSimd.IsSupported) + { + ProcessMixAdvSimd(outputMix, inputMix); + } + else + { + ProcessMixSlowPath(outputMix, inputMix); + } + } + + public void Process(CommandList context) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + ProcessMix(outputBuffer, inputBuffer); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs new file mode 100644 index 00000000..76a1aba2 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs @@ -0,0 +1,68 @@ +using Ryujinx.Audio.Renderer.Common; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class MixRampCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.MixRamp; + + public uint EstimatedProcessingTime { get; set; } + + public ushort InputBufferIndex { get; } + public ushort OutputBufferIndex { get; } + + public float Volume0 { get; } + public float Volume1 { get; } + + public Memory State { get; } + + public int LastSampleIndex { get; } + + public MixRampCommand(float volume0, float volume1, uint inputBufferIndex, uint outputBufferIndex, int lastSampleIndex, Memory state, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndex = (ushort)inputBufferIndex; + OutputBufferIndex = (ushort)outputBufferIndex; + + Volume0 = volume0; + Volume1 = volume1; + + State = state; + LastSampleIndex = lastSampleIndex; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float ProcessMixRamp(Span outputBuffer, ReadOnlySpan inputBuffer, int sampleCount) + { + float ramp = (Volume1 - Volume0) / sampleCount; + float volume = Volume0; + float state = 0; + + for (int i = 0; i < sampleCount; i++) + { + state = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], volume); + + outputBuffer[i] += state; + volume += ramp; + } + + return state; + } + + public void Process(CommandList context) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + State.Span[0].LastSamples[LastSampleIndex] = ProcessMixRamp(outputBuffer, inputBuffer, (int)context.SampleCount); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs new file mode 100644 index 00000000..e348e358 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs @@ -0,0 +1,91 @@ +using Ryujinx.Audio.Renderer.Common; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class MixRampGroupedCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.MixRampGrouped; + + public uint EstimatedProcessingTime { get; set; } + + public uint MixBufferCount { get; } + + public ushort[] InputBufferIndices { get; } + public ushort[] OutputBufferIndices { get; } + + public float[] Volume0 { get; } + public float[] Volume1 { get; } + + public Memory State { get; } + + public MixRampGroupedCommand(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span volume0, Span volume1, Memory state, int nodeId) + { + Enabled = true; + MixBufferCount = mixBufferCount; + NodeId = nodeId; + + InputBufferIndices = new ushort[Constants.MixBufferCountMax]; + OutputBufferIndices = new ushort[Constants.MixBufferCountMax]; + Volume0 = new float[Constants.MixBufferCountMax]; + Volume1 = new float[Constants.MixBufferCountMax]; + + for (int i = 0; i < mixBufferCount; i++) + { + InputBufferIndices[i] = (ushort)inputBufferIndex; + OutputBufferIndices[i] = (ushort)(outputBufferIndex + i); + + Volume0[i] = volume0[i]; + Volume1[i] = volume1[i]; + } + + State = state; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float ProcessMixRampGrouped(Span outputBuffer, ReadOnlySpan inputBuffer, float volume0, float volume1, int sampleCount) + { + float ramp = (volume1 - volume0) / sampleCount; + float volume = volume0; + float state = 0; + + for (int i = 0; i < sampleCount; i++) + { + state = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], volume); + + outputBuffer[i] += state; + volume += ramp; + } + + return state; + } + + public void Process(CommandList context) + { + for (int i = 0; i < MixBufferCount; i++) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndices[i]); + Span outputBuffer = context.GetBuffer(OutputBufferIndices[i]); + + float volume0 = Volume0[i]; + float volume1 = Volume1[i]; + + ref VoiceUpdateState state = ref State.Span[0]; + + if (volume0 != 0 || volume1 != 0) + { + state.LastSamples[i] = ProcessMixRampGrouped(outputBuffer, inputBuffer, volume0, volume1, (int)context.SampleCount); + } + else + { + state.LastSamples[i] = 0; + } + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs new file mode 100644 index 00000000..7cec7d2a --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs @@ -0,0 +1,74 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using System; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class PcmFloatDataSourceCommandVersion1 : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.PcmFloatDataSourceVersion1; + + public uint EstimatedProcessingTime { get; set; } + + public ushort OutputBufferIndex { get; } + public uint SampleRate { get; } + public uint ChannelIndex { get; } + + public uint ChannelCount { get; } + + public float Pitch { get; } + + public WaveBuffer[] WaveBuffers { get; } + + public Memory State { get; } + public DecodingBehaviour DecodingBehaviour { get; } + + public PcmFloatDataSourceCommandVersion1(ref Server.Voice.VoiceState serverState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + OutputBufferIndex = (ushort)(channelIndex + outputBufferIndex); + SampleRate = serverState.SampleRate; + ChannelIndex = channelIndex; + ChannelCount = serverState.ChannelsCount; + Pitch = serverState.Pitch; + + WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount]; + + for (int i = 0; i < WaveBuffers.Length; i++) + { + ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i]; + + WaveBuffers[i] = voiceWaveBuffer.ToCommon(1); + } + + State = state; + DecodingBehaviour = serverState.DecodingBehaviour; + } + + public void Process(CommandList context) + { + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + DataSourceHelper.WaveBufferInformation info = new DataSourceHelper.WaveBufferInformation + { + SourceSampleRate = SampleRate, + SampleFormat = SampleFormat.PcmFloat, + Pitch = Pitch, + DecodingBehaviour = DecodingBehaviour, + ExtraParameter = 0, + ExtraParameterSize = 0, + ChannelIndex = (int)ChannelIndex, + ChannelCount = (int)ChannelCount, + }; + + DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, ref info, WaveBuffers, ref State.Span[0], context.SampleRate, (int)context.SampleCount); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs new file mode 100644 index 00000000..dfe9814f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs @@ -0,0 +1,74 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using System; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class PcmInt16DataSourceCommandVersion1 : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.PcmInt16DataSourceVersion1; + + public uint EstimatedProcessingTime { get; set; } + + public ushort OutputBufferIndex { get; } + public uint SampleRate { get; } + public uint ChannelIndex { get; } + + public uint ChannelCount { get; } + + public float Pitch { get; } + + public WaveBuffer[] WaveBuffers { get; } + + public Memory State { get; } + public DecodingBehaviour DecodingBehaviour { get; } + + public PcmInt16DataSourceCommandVersion1(ref Server.Voice.VoiceState serverState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + OutputBufferIndex = (ushort)(channelIndex + outputBufferIndex); + SampleRate = serverState.SampleRate; + ChannelIndex = channelIndex; + ChannelCount = serverState.ChannelsCount; + Pitch = serverState.Pitch; + + WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount]; + + for (int i = 0; i < WaveBuffers.Length; i++) + { + ref Server.Voice.WaveBuffer voiceWaveBuffer = ref serverState.WaveBuffers[i]; + + WaveBuffers[i] = voiceWaveBuffer.ToCommon(1); + } + + State = state; + DecodingBehaviour = serverState.DecodingBehaviour; + } + + public void Process(CommandList context) + { + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + DataSourceHelper.WaveBufferInformation info = new DataSourceHelper.WaveBufferInformation + { + SourceSampleRate = SampleRate, + SampleFormat = SampleFormat.PcmInt16, + Pitch = Pitch, + DecodingBehaviour = DecodingBehaviour, + ExtraParameter = 0, + ExtraParameterSize = 0, + ChannelIndex = (int)ChannelIndex, + ChannelCount = (int)ChannelCount, + }; + + DataSourceHelper.ProcessWaveBuffers(context.MemoryManager, outputBuffer, ref info, WaveBuffers, ref State.Span[0], context.SampleRate, (int)context.SampleCount); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs new file mode 100644 index 00000000..d3e3f805 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs @@ -0,0 +1,47 @@ +using Ryujinx.Audio.Renderer.Server.Performance; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class PerformanceCommand : ICommand + { + public enum Type + { + Invalid, + Start, + End + } + + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.Performance; + + public uint EstimatedProcessingTime { get; set; } + + public PerformanceEntryAddresses PerformanceEntryAddresses { get; } + + public Type PerformanceType { get; set; } + + public PerformanceCommand(ref PerformanceEntryAddresses performanceEntryAddresses, Type performanceType, int nodeId) + { + Enabled = true; + PerformanceEntryAddresses = performanceEntryAddresses; + PerformanceType = performanceType; + NodeId = nodeId; + } + + public void Process(CommandList context) + { + if (PerformanceType == Type.Start) + { + PerformanceEntryAddresses.SetStartTime(context.GetTimeElapsedSinceDspStartedProcessing()); + } + else if (PerformanceType == Type.End) + { + PerformanceEntryAddresses.SetProcessingTime(context.GetTimeElapsedSinceDspStartedProcessing()); + PerformanceEntryAddresses.IncrementEntryCount(); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs new file mode 100644 index 00000000..eeb64567 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs @@ -0,0 +1,254 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.Effect; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class Reverb3dCommand : ICommand + { + private static readonly int[] OutputEarlyIndicesTableMono = new int[20] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + private static readonly int[] TargetEarlyDelayLineIndicesTableMono = new int[20] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }; + private static readonly int[] TargetOutputFeedbackIndicesTableMono = new int[1] { 0 }; + + private static readonly int[] OutputEarlyIndicesTableStereo = new int[20] { 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1 }; + private static readonly int[] TargetEarlyDelayLineIndicesTableStereo = new int[20] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }; + private static readonly int[] TargetOutputFeedbackIndicesTableStereo = new int[2] { 0, 1 }; + + private static readonly int[] OutputEarlyIndicesTableQuadraphonic = new int[20] { 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 3, 3, 3 }; + private static readonly int[] TargetEarlyDelayLineIndicesTableQuadraphonic = new int[20] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }; + private static readonly int[] TargetOutputFeedbackIndicesTableQuadraphonic = new int[4] { 0, 1, 2, 3 }; + + private static readonly int[] OutputEarlyIndicesTableSurround = new int[40] { 4, 5, 0, 5, 0, 5, 1, 5, 1, 5, 1, 5, 1, 5, 2, 5, 2, 5, 2, 5, 1, 5, 1, 5, 1, 5, 0, 5, 0, 5, 0, 5, 0, 5, 3, 5, 3, 5, 3, 5 }; + private static readonly int[] TargetEarlyDelayLineIndicesTableSurround = new int[40] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19 }; + private static readonly int[] TargetOutputFeedbackIndicesTableSurround = new int[6] { 0, 1, 2, 3, -1, 3 }; + + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.Reverb3d; + + public uint EstimatedProcessingTime { get; set; } + + public ushort InputBufferIndex { get; } + public ushort OutputBufferIndex { get; } + + public Reverb3dParameter Parameter => _parameter; + public Memory State { get; } + public ulong WorkBuffer { get; } + public ushort[] OutputBufferIndices { get; } + public ushort[] InputBufferIndices { get; } + + public bool IsEffectEnabled { get; } + + private Reverb3dParameter _parameter; + + public Reverb3dCommand(uint bufferOffset, Reverb3dParameter parameter, Memory state, bool isEnabled, ulong workBuffer, int nodeId, bool newEffectChannelMappingSupported) + { + Enabled = true; + IsEffectEnabled = isEnabled; + NodeId = nodeId; + _parameter = parameter; + State = state; + WorkBuffer = workBuffer; + + InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]); + OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); + } + + // NOTE: We do the opposite as Nintendo here for now to restore previous behaviour + // TODO: Update reverb 3d processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping. + DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices); + DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverb3dMono(ref Reverb3dState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverb3dGeneric(ref state, outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableMono, TargetEarlyDelayLineIndicesTableMono, TargetOutputFeedbackIndicesTableMono); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverb3dStereo(ref Reverb3dState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverb3dGeneric(ref state, outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableStereo, TargetEarlyDelayLineIndicesTableStereo, TargetOutputFeedbackIndicesTableStereo); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverb3dQuadraphonic(ref Reverb3dState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverb3dGeneric(ref state, outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableQuadraphonic, TargetEarlyDelayLineIndicesTableQuadraphonic, TargetOutputFeedbackIndicesTableQuadraphonic); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverb3dSurround(ref Reverb3dState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverb3dGeneric(ref state, outputBuffers, inputBuffers, sampleCount, OutputEarlyIndicesTableSurround, TargetEarlyDelayLineIndicesTableSurround, TargetOutputFeedbackIndicesTableSurround); + } + + private unsafe void ProcessReverb3dGeneric(ref Reverb3dState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount, ReadOnlySpan outputEarlyIndicesTable, ReadOnlySpan targetEarlyDelayLineIndicesTable, ReadOnlySpan targetOutputFeedbackIndicesTable) + { + const int delayLineSampleIndexOffset = 1; + + bool isMono = Parameter.ChannelCount == 1; + bool isSurround = Parameter.ChannelCount == 6; + + Span outputValues = stackalloc float[Constants.ChannelCountMax]; + Span channelInput = stackalloc float[Parameter.ChannelCount]; + Span feedbackValues = stackalloc float[4]; + Span feedbackOutputValues = stackalloc float[4]; + Span values = stackalloc float[4]; + + for (int sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) + { + outputValues.Fill(0); + + float tapOut = state.PreDelayLine.TapUnsafe(state.ReflectionDelayTime, delayLineSampleIndexOffset); + + for (int i = 0; i < targetEarlyDelayLineIndicesTable.Length; i++) + { + int earlyDelayIndex = targetEarlyDelayLineIndicesTable[i]; + int outputIndex = outputEarlyIndicesTable[i]; + + float tempTapOut = state.PreDelayLine.TapUnsafe(state.EarlyDelayTime[earlyDelayIndex], delayLineSampleIndexOffset); + + outputValues[outputIndex] += tempTapOut * state.EarlyGain[earlyDelayIndex]; + } + + float targetPreDelayValue = 0; + + for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++) + { + channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex); + targetPreDelayValue += channelInput[channelIndex]; + } + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + outputValues[i] *= state.EarlyReflectionsGain; + } + + state.PreviousPreDelayValue = (targetPreDelayValue * state.TargetPreDelayGain) + (state.PreviousPreDelayValue * state.PreviousPreDelayGain); + + state.PreDelayLine.Update(state.PreviousPreDelayValue); + + for (int i = 0; i < state.FdnDelayLines.Length; i++) + { + float fdnValue = state.FdnDelayLines[i].Read(); + + float feedbackOutputValue = fdnValue * state.DecayDirectFdnGain[i] + state.PreviousFeedbackOutputDecayed[i]; + + state.PreviousFeedbackOutputDecayed[i] = (fdnValue * state.DecayCurrentFdnGain[i]) + (feedbackOutputValue * state.DecayCurrentOutputGain[i]); + + feedbackOutputValues[i] = feedbackOutputValue; + } + + feedbackValues[0] = feedbackOutputValues[2] + feedbackOutputValues[1]; + feedbackValues[1] = -feedbackOutputValues[0] - feedbackOutputValues[3]; + feedbackValues[2] = feedbackOutputValues[0] - feedbackOutputValues[3]; + feedbackValues[3] = feedbackOutputValues[1] - feedbackOutputValues[2]; + + for (int i = 0; i < state.DecayDelays1.Length; i++) + { + float temp = state.DecayDelays1[i].Update(tapOut * state.LateReverbGain + feedbackValues[i]); + + values[i] = state.DecayDelays2[i].Update(temp); + + state.FdnDelayLines[i].Update(values[i]); + } + + for (int channelIndex = 0; channelIndex < targetOutputFeedbackIndicesTable.Length; channelIndex++) + { + int targetOutputFeedbackIndex = targetOutputFeedbackIndicesTable[channelIndex]; + + if (targetOutputFeedbackIndex >= 0) + { + *((float*)outputBuffers[channelIndex] + sampleIndex) = (outputValues[channelIndex] + values[targetOutputFeedbackIndex] + channelInput[channelIndex] * state.DryGain); + } + } + + if (isMono) + { + *((float*)outputBuffers[0] + sampleIndex) += values[1]; + } + + if (isSurround) + { + *((float*)outputBuffers[4] + sampleIndex) += (outputValues[4] + state.FrontCenterDelayLine.Update((values[2] - values[3]) * 0.5f) + channelInput[4] * state.DryGain); + } + } + } + + public void ProcessReverb3d(CommandList context, ref Reverb3dState state) + { + Debug.Assert(Parameter.IsChannelCountValid()); + + if (IsEffectEnabled && Parameter.IsChannelCountValid()) + { + Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]); + outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]); + } + + switch (Parameter.ChannelCount) + { + case 1: + ProcessReverb3dMono(ref state, outputBuffers, inputBuffers, context.SampleCount); + break; + case 2: + ProcessReverb3dStereo(ref state, outputBuffers, inputBuffers, context.SampleCount); + break; + case 4: + ProcessReverb3dQuadraphonic(ref state, outputBuffers, inputBuffers, context.SampleCount); + break; + case 6: + ProcessReverb3dSurround(ref state, outputBuffers, inputBuffers, context.SampleCount); + break; + default: + throw new NotImplementedException(Parameter.ChannelCount.ToString()); + } + } + else + { + for (int i = 0; i < Parameter.ChannelCount; i++) + { + if (InputBufferIndices[i] != OutputBufferIndices[i]) + { + context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]); + } + } + } + } + + public void Process(CommandList context) + { + ref Reverb3dState state = ref State.Span[0]; + + if (IsEffectEnabled) + { + if (Parameter.ParameterStatus == UsageState.Invalid) + { + state = new Reverb3dState(ref _parameter, WorkBuffer); + } + else if (Parameter.ParameterStatus == UsageState.New) + { + state.UpdateParameter(ref _parameter); + } + } + + ProcessReverb3d(context, ref state); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs new file mode 100644 index 00000000..0a32a065 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs @@ -0,0 +1,279 @@ +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class ReverbCommand : ICommand + { + private static readonly int[] OutputEarlyIndicesTableMono = new int[10] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + private static readonly int[] TargetEarlyDelayLineIndicesTableMono = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + private static readonly int[] OutputIndicesTableMono = new int[4] { 0, 0, 0, 0 }; + private static readonly int[] TargetOutputFeedbackIndicesTableMono = new int[4] { 0, 1, 2, 3 }; + + private static readonly int[] OutputEarlyIndicesTableStereo = new int[10] { 0, 0, 1, 1, 0, 1, 0, 0, 1, 1 }; + private static readonly int[] TargetEarlyDelayLineIndicesTableStereo = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + private static readonly int[] OutputIndicesTableStereo = new int[4] { 0, 0, 1, 1 }; + private static readonly int[] TargetOutputFeedbackIndicesTableStereo = new int[4] { 2, 0, 3, 1 }; + + private static readonly int[] OutputEarlyIndicesTableQuadraphonic = new int[10] { 0, 0, 1, 1, 0, 1, 2, 2, 3, 3 }; + private static readonly int[] TargetEarlyDelayLineIndicesTableQuadraphonic = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + private static readonly int[] OutputIndicesTableQuadraphonic = new int[4] { 0, 1, 2, 3 }; + private static readonly int[] TargetOutputFeedbackIndicesTableQuadraphonic = new int[4] { 0, 1, 2, 3 }; + + private static readonly int[] OutputEarlyIndicesTableSurround = new int[20] { 0, 5, 0, 5, 1, 5, 1, 5, 4, 5, 4, 5, 2, 5, 2, 5, 3, 5, 3, 5 }; + private static readonly int[] TargetEarlyDelayLineIndicesTableSurround = new int[20] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9 }; + private static readonly int[] OutputIndicesTableSurround = new int[Constants.ChannelCountMax] { 0, 1, 2, 3, 4, 5 }; + private static readonly int[] TargetOutputFeedbackIndicesTableSurround = new int[Constants.ChannelCountMax] { 0, 1, 2, 3, -1, 3 }; + + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.Reverb; + + public uint EstimatedProcessingTime { get; set; } + + public ReverbParameter Parameter => _parameter; + public Memory State { get; } + public ulong WorkBuffer { get; } + public ushort[] OutputBufferIndices { get; } + public ushort[] InputBufferIndices { get; } + public bool IsLongSizePreDelaySupported { get; } + + public bool IsEffectEnabled { get; } + + private ReverbParameter _parameter; + + private const int FixedPointPrecision = 14; + + public ReverbCommand(uint bufferOffset, ReverbParameter parameter, Memory state, bool isEnabled, ulong workBuffer, int nodeId, bool isLongSizePreDelaySupported, bool newEffectChannelMappingSupported) + { + Enabled = true; + IsEffectEnabled = isEnabled; + NodeId = nodeId; + _parameter = parameter; + State = state; + WorkBuffer = workBuffer; + + InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + InputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Input[i]); + OutputBufferIndices[i] = (ushort)(bufferOffset + Parameter.Output[i]); + } + + IsLongSizePreDelaySupported = isLongSizePreDelaySupported; + + // NOTE: We do the opposite as Nintendo here for now to restore previous behaviour + // TODO: Update reverb processing and remove this to use RemapLegacyChannelEffectMappingToChannelResourceMapping. + DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, InputBufferIndices); + DataSourceHelper.RemapChannelResourceMappingToLegacy(newEffectChannelMappingSupported, OutputBufferIndices); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverbMono(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverbGeneric(ref state, + outputBuffers, + inputBuffers, + sampleCount, + OutputEarlyIndicesTableMono, + TargetEarlyDelayLineIndicesTableMono, + TargetOutputFeedbackIndicesTableMono, + OutputIndicesTableMono); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverbStereo(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverbGeneric(ref state, + outputBuffers, + inputBuffers, + sampleCount, + OutputEarlyIndicesTableStereo, + TargetEarlyDelayLineIndicesTableStereo, + TargetOutputFeedbackIndicesTableStereo, + OutputIndicesTableStereo); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverbQuadraphonic(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverbGeneric(ref state, + outputBuffers, + inputBuffers, + sampleCount, + OutputEarlyIndicesTableQuadraphonic, + TargetEarlyDelayLineIndicesTableQuadraphonic, + TargetOutputFeedbackIndicesTableQuadraphonic, + OutputIndicesTableQuadraphonic); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessReverbSurround(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) + { + ProcessReverbGeneric(ref state, + outputBuffers, + inputBuffers, + sampleCount, + OutputEarlyIndicesTableSurround, + TargetEarlyDelayLineIndicesTableSurround, + TargetOutputFeedbackIndicesTableSurround, + OutputIndicesTableSurround); + } + + private unsafe void ProcessReverbGeneric(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount, ReadOnlySpan outputEarlyIndicesTable, ReadOnlySpan targetEarlyDelayLineIndicesTable, ReadOnlySpan targetOutputFeedbackIndicesTable, ReadOnlySpan outputIndicesTable) + { + bool isSurround = Parameter.ChannelCount == 6; + + float reverbGain = FixedPointHelper.ToFloat(Parameter.ReverbGain, FixedPointPrecision); + float lateGain = FixedPointHelper.ToFloat(Parameter.LateGain, FixedPointPrecision); + float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision); + float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision); + + Span outputValues = stackalloc float[Constants.ChannelCountMax]; + Span feedbackValues = stackalloc float[4]; + Span feedbackOutputValues = stackalloc float[4]; + Span channelInput = stackalloc float[Parameter.ChannelCount]; + + for (int sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) + { + outputValues.Fill(0); + + for (int i = 0; i < targetEarlyDelayLineIndicesTable.Length; i++) + { + int earlyDelayIndex = targetEarlyDelayLineIndicesTable[i]; + int outputIndex = outputEarlyIndicesTable[i]; + + float tapOutput = state.PreDelayLine.TapUnsafe(state.EarlyDelayTime[earlyDelayIndex], 0); + + outputValues[outputIndex] += tapOutput * state.EarlyGain[earlyDelayIndex]; + } + + if (isSurround) + { + outputValues[5] *= 0.2f; + } + + float targetPreDelayValue = 0; + + for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++) + { + channelInput[channelIndex] = *((float*)inputBuffers[channelIndex] + sampleIndex) * 64; + targetPreDelayValue += channelInput[channelIndex] * reverbGain; + } + + state.PreDelayLine.Update(targetPreDelayValue); + + float lateValue = state.PreDelayLine.Tap(state.PreDelayLineDelayTime) * lateGain; + + for (int i = 0; i < state.FdnDelayLines.Length; i++) + { + feedbackOutputValues[i] = state.FdnDelayLines[i].Read() * state.HighFrequencyDecayDirectGain[i] + state.PreviousFeedbackOutput[i] * state.HighFrequencyDecayPreviousGain[i]; + state.PreviousFeedbackOutput[i] = feedbackOutputValues[i]; + } + + feedbackValues[0] = feedbackOutputValues[2] + feedbackOutputValues[1]; + feedbackValues[1] = -feedbackOutputValues[0] - feedbackOutputValues[3]; + feedbackValues[2] = feedbackOutputValues[0] - feedbackOutputValues[3]; + feedbackValues[3] = feedbackOutputValues[1] - feedbackOutputValues[2]; + + for (int i = 0; i < state.FdnDelayLines.Length; i++) + { + feedbackOutputValues[i] = state.DecayDelays[i].Update(feedbackValues[i] + lateValue); + state.FdnDelayLines[i].Update(feedbackOutputValues[i]); + } + + for (int i = 0; i < targetOutputFeedbackIndicesTable.Length; i++) + { + int targetOutputFeedbackIndex = targetOutputFeedbackIndicesTable[i]; + int outputIndex = outputIndicesTable[i]; + + if (targetOutputFeedbackIndex >= 0) + { + outputValues[outputIndex] += feedbackOutputValues[targetOutputFeedbackIndex]; + } + } + + if (isSurround) + { + outputValues[4] += state.FrontCenterDelayLine.Update((feedbackOutputValues[2] - feedbackOutputValues[3]) * 0.5f); + } + + for (int channelIndex = 0; channelIndex < Parameter.ChannelCount; channelIndex++) + { + *((float*)outputBuffers[channelIndex] + sampleIndex) = (outputValues[channelIndex] * outGain + channelInput[channelIndex] * dryGain) / 64; + } + } + } + + private void ProcessReverb(CommandList context, ref ReverbState state) + { + Debug.Assert(Parameter.IsChannelCountValid()); + + if (IsEffectEnabled && Parameter.IsChannelCountValid()) + { + Span inputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + Span outputBuffers = stackalloc IntPtr[Parameter.ChannelCount]; + + for (int i = 0; i < Parameter.ChannelCount; i++) + { + inputBuffers[i] = context.GetBufferPointer(InputBufferIndices[i]); + outputBuffers[i] = context.GetBufferPointer(OutputBufferIndices[i]); + } + + switch (Parameter.ChannelCount) + { + case 1: + ProcessReverbMono(ref state, outputBuffers, inputBuffers, context.SampleCount); + break; + case 2: + ProcessReverbStereo(ref state, outputBuffers, inputBuffers, context.SampleCount); + break; + case 4: + ProcessReverbQuadraphonic(ref state, outputBuffers, inputBuffers, context.SampleCount); + break; + case 6: + ProcessReverbSurround(ref state, outputBuffers, inputBuffers, context.SampleCount); + break; + default: + throw new NotImplementedException(Parameter.ChannelCount.ToString()); + } + } + else + { + for (int i = 0; i < Parameter.ChannelCount; i++) + { + if (InputBufferIndices[i] != OutputBufferIndices[i]) + { + context.CopyBuffer(OutputBufferIndices[i], InputBufferIndices[i]); + } + } + } + } + + public void Process(CommandList context) + { + ref ReverbState state = ref State.Span[0]; + + if (IsEffectEnabled) + { + if (Parameter.Status == Server.Effect.UsageState.Invalid) + { + state = new ReverbState(ref _parameter, WorkBuffer, IsLongSizePreDelaySupported); + } + else if (Parameter.Status == Server.Effect.UsageState.New) + { + state.UpdateParameter(ref _parameter); + } + } + + ProcessReverb(context, ref state); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs new file mode 100644 index 00000000..0870d59c --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs @@ -0,0 +1,70 @@ +using Ryujinx.Audio.Renderer.Server.Upsampler; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class UpsampleCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.Upsample; + + public uint EstimatedProcessingTime { get; set; } + + public uint BufferCount { get; } + public uint InputBufferIndex { get; } + public uint InputSampleCount { get; } + public uint InputSampleRate { get; } + + public UpsamplerState UpsamplerInfo { get; } + + public Memory OutBuffer { get; } + + public UpsampleCommand(uint bufferOffset, UpsamplerState info, uint inputCount, Span inputBufferOffset, uint bufferCount, uint sampleCount, uint sampleRate, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndex = 0; + OutBuffer = info.OutputBuffer; + BufferCount = bufferCount; + InputSampleCount = sampleCount; + InputSampleRate = sampleRate; + info.SourceSampleCount = inputCount; + info.InputBufferIndices = new ushort[inputCount]; + + for (int i = 0; i < inputCount; i++) + { + info.InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]); + } + + if (info.BufferStates?.Length != (int)inputCount) + { + // Keep state if possible. + info.BufferStates = new UpsamplerBufferState[(int)inputCount]; + } + + UpsamplerInfo = info; + } + + private Span GetBuffer(int index, int sampleCount) + { + return UpsamplerInfo.OutputBuffer.Span.Slice(index * sampleCount, sampleCount); + } + + public void Process(CommandList context) + { + uint bufferCount = Math.Min(BufferCount, UpsamplerInfo.SourceSampleCount); + + for (int i = 0; i < bufferCount; i++) + { + Span inputBuffer = context.GetBuffer(UpsamplerInfo.InputBufferIndices[i]); + Span outputBuffer = GetBuffer(UpsamplerInfo.InputBufferIndices[i], (int)UpsamplerInfo.SampleCount); + + UpsamplerHelper.Upsample(outputBuffer, inputBuffer, (int)UpsamplerInfo.SampleCount, (int)InputSampleCount, ref UpsamplerInfo.BufferStates[i]); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs new file mode 100644 index 00000000..0628f6d8 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs @@ -0,0 +1,137 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Intrinsics.X86; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class VolumeCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.Volume; + + public uint EstimatedProcessingTime { get; set; } + + public ushort InputBufferIndex { get; } + public ushort OutputBufferIndex { get; } + + public float Volume { get; } + + public VolumeCommand(float volume, uint bufferIndex, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndex = (ushort)bufferIndex; + OutputBufferIndex = (ushort)bufferIndex; + + Volume = volume; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessVolumeAvx(Span outputBuffer, ReadOnlySpan inputBuffer) + { + Vector256 volumeVec = Vector256.Create(Volume); + + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputBuffer); + Span> outputVec = MemoryMarshal.Cast>(outputBuffer); + + int sisdStart = inputVec.Length * 8; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Avx.Ceiling(Avx.Multiply(inputVec[i], volumeVec)); + } + + for (int i = sisdStart; i < inputBuffer.Length; i++) + { + outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessVolumeSse41(Span outputBuffer, ReadOnlySpan inputBuffer) + { + Vector128 volumeVec = Vector128.Create(Volume); + + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputBuffer); + Span> outputVec = MemoryMarshal.Cast>(outputBuffer); + + int sisdStart = inputVec.Length * 4; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Sse41.Ceiling(Sse.Multiply(inputVec[i], volumeVec)); + } + + for (int i = sisdStart; i < inputBuffer.Length; i++) + { + outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessVolumeAdvSimd(Span outputBuffer, ReadOnlySpan inputBuffer) + { + Vector128 volumeVec = Vector128.Create(Volume); + + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(inputBuffer); + Span> outputVec = MemoryMarshal.Cast>(outputBuffer); + + int sisdStart = inputVec.Length * 4; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = AdvSimd.Ceiling(AdvSimd.Multiply(inputVec[i], volumeVec)); + } + + for (int i = sisdStart; i < inputBuffer.Length; i++) + { + outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessVolume(Span outputBuffer, ReadOnlySpan inputBuffer) + { + if (Avx.IsSupported) + { + ProcessVolumeAvx(outputBuffer, inputBuffer); + } + else if (Sse41.IsSupported) + { + ProcessVolumeSse41(outputBuffer, inputBuffer); + } + else if (AdvSimd.IsSupported) + { + ProcessVolumeAdvSimd(outputBuffer, inputBuffer); + } + else + { + ProcessVolumeSlowPath(outputBuffer, inputBuffer); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessVolumeSlowPath(Span outputBuffer, ReadOnlySpan inputBuffer) + { + for (int i = 0; i < outputBuffer.Length; i++) + { + outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], Volume); + } + } + + public void Process(CommandList context) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + ProcessVolume(outputBuffer, inputBuffer); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs new file mode 100644 index 00000000..5c0c8845 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs @@ -0,0 +1,56 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Command +{ + public class VolumeRampCommand : ICommand + { + public bool Enabled { get; set; } + + public int NodeId { get; } + + public CommandType CommandType => CommandType.VolumeRamp; + + public uint EstimatedProcessingTime { get; set; } + + public ushort InputBufferIndex { get; } + public ushort OutputBufferIndex { get; } + + public float Volume0 { get; } + public float Volume1 { get; } + + public VolumeRampCommand(float volume0, float volume1, uint bufferIndex, int nodeId) + { + Enabled = true; + NodeId = nodeId; + + InputBufferIndex = (ushort)bufferIndex; + OutputBufferIndex = (ushort)bufferIndex; + + Volume0 = volume0; + Volume1 = volume1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ProcessVolumeRamp(Span outputBuffer, ReadOnlySpan inputBuffer, int sampleCount) + { + float ramp = (Volume1 - Volume0) / sampleCount; + + float volume = Volume0; + + for (int i = 0; i < sampleCount; i++) + { + outputBuffer[i] = FloatingPointHelper.MultiplyRoundUp(inputBuffer[i], volume); + volume += ramp; + } + } + + public void Process(CommandList context) + { + ReadOnlySpan inputBuffer = context.GetBuffer(InputBufferIndex); + Span outputBuffer = context.GetBuffer(OutputBufferIndex); + + ProcessVolumeRamp(outputBuffer, inputBuffer, (int)context.SampleCount); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs new file mode 100644 index 00000000..5ca1ddba --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs @@ -0,0 +1,466 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Arm; +using System.Runtime.Intrinsics.X86; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class DataSourceHelper + { + private const int FixedPointPrecision = 15; + + public struct WaveBufferInformation + { + public uint SourceSampleRate; + public float Pitch; + public ulong ExtraParameter; + public ulong ExtraParameterSize; + public int ChannelIndex; + public int ChannelCount; + public DecodingBehaviour DecodingBehaviour; + public SampleRateConversionQuality SrcQuality; + public SampleFormat SampleFormat; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetPitchLimitBySrcQuality(SampleRateConversionQuality quality) + { + return quality switch + { + SampleRateConversionQuality.Default or SampleRateConversionQuality.Low => 4, + SampleRateConversionQuality.High => 8, + _ => throw new ArgumentException(quality.ToString()), + }; + } + + public static void ProcessWaveBuffers(IVirtualMemoryManager memoryManager, Span outputBuffer, ref WaveBufferInformation info, Span wavebuffers, ref VoiceUpdateState voiceState, uint targetSampleRate, int sampleCount) + { + const int tempBufferSize = 0x3F00; + + Span tempBuffer = stackalloc short[tempBufferSize]; + + float sampleRateRatio = (float)info.SourceSampleRate / targetSampleRate * info.Pitch; + + float fraction = voiceState.Fraction; + int waveBufferIndex = (int)voiceState.WaveBufferIndex; + ulong playedSampleCount = voiceState.PlayedSampleCount; + int offset = voiceState.Offset; + uint waveBufferConsumed = voiceState.WaveBufferConsumed; + + int pitchMaxLength = GetPitchLimitBySrcQuality(info.SrcQuality); + + int totalNeededSize = (int)MathF.Truncate(fraction + sampleRateRatio * sampleCount); + + if (totalNeededSize + pitchMaxLength <= tempBufferSize && totalNeededSize >= 0) + { + int sourceSampleCountToProcess = sampleCount; + + int maxSampleCountPerIteration = Math.Min((int)MathF.Truncate((tempBufferSize - fraction) / sampleRateRatio), sampleCount); + + bool isStarving = false; + + int i = 0; + + while (i < sourceSampleCountToProcess) + { + int tempBufferIndex = 0; + + if (!info.DecodingBehaviour.HasFlag(DecodingBehaviour.SkipPitchAndSampleRateConversion)) + { + voiceState.Pitch.AsSpan().Slice(0, pitchMaxLength).CopyTo(tempBuffer); + tempBufferIndex += pitchMaxLength; + } + + int sampleCountToProcess = Math.Min(sourceSampleCountToProcess, maxSampleCountPerIteration); + + int y = 0; + + int sampleCountToDecode = (int)MathF.Truncate(fraction + sampleRateRatio * sampleCountToProcess); + + while (y < sampleCountToDecode) + { + if (waveBufferIndex >= Constants.VoiceWaveBufferCount) + { + waveBufferIndex = 0; + playedSampleCount = 0; + } + + if (!voiceState.IsWaveBufferValid[waveBufferIndex]) + { + isStarving = true; + break; + } + + ref WaveBuffer waveBuffer = ref wavebuffers[waveBufferIndex]; + + if (offset == 0 && info.SampleFormat == SampleFormat.Adpcm && waveBuffer.Context != 0) + { + voiceState.LoopContext = memoryManager.Read(waveBuffer.Context); + } + + Span tempSpan = tempBuffer.Slice(tempBufferIndex + y); + + int decodedSampleCount = -1; + + int targetSampleStartOffset; + int targetSampleEndOffset; + + if (voiceState.LoopCount > 0 && waveBuffer.LoopStartSampleOffset != 0 && waveBuffer.LoopEndSampleOffset != 0 && waveBuffer.LoopStartSampleOffset <= waveBuffer.LoopEndSampleOffset) + { + targetSampleStartOffset = (int)waveBuffer.LoopStartSampleOffset; + targetSampleEndOffset = (int)waveBuffer.LoopEndSampleOffset; + } + else + { + targetSampleStartOffset = (int)waveBuffer.StartSampleOffset; + targetSampleEndOffset = (int)waveBuffer.EndSampleOffset; + } + + int targetWaveBufferSampleCount = targetSampleEndOffset - targetSampleStartOffset; + + switch (info.SampleFormat) + { + case SampleFormat.Adpcm: + ReadOnlySpan waveBufferAdpcm = ReadOnlySpan.Empty; + + if (waveBuffer.Buffer != 0 && waveBuffer.BufferSize != 0) + { + // TODO: we are possibly copying a lot of unneeded data here, we should only take what we need. + waveBufferAdpcm = memoryManager.GetSpan(waveBuffer.Buffer, (int)waveBuffer.BufferSize); + } + + ReadOnlySpan coefficients = MemoryMarshal.Cast(memoryManager.GetSpan(info.ExtraParameter, (int)info.ExtraParameterSize)); + decodedSampleCount = AdpcmHelper.Decode(tempSpan, waveBufferAdpcm, targetSampleStartOffset, targetSampleEndOffset, offset, sampleCountToDecode - y, coefficients, ref voiceState.LoopContext); + break; + case SampleFormat.PcmInt16: + ReadOnlySpan waveBufferPcm16 = ReadOnlySpan.Empty; + + if (waveBuffer.Buffer != 0 && waveBuffer.BufferSize != 0) + { + ulong bufferOffset = waveBuffer.Buffer + PcmHelper.GetBufferOffset(targetSampleStartOffset, offset, info.ChannelCount); + int bufferSize = PcmHelper.GetBufferSize(targetSampleStartOffset, targetSampleEndOffset, offset, sampleCountToDecode - y) * info.ChannelCount; + + waveBufferPcm16 = MemoryMarshal.Cast(memoryManager.GetSpan(bufferOffset, bufferSize)); + } + + decodedSampleCount = PcmHelper.Decode(tempSpan, waveBufferPcm16, targetSampleStartOffset, targetSampleEndOffset, info.ChannelIndex, info.ChannelCount); + break; + case SampleFormat.PcmFloat: + ReadOnlySpan waveBufferPcmFloat = ReadOnlySpan.Empty; + + if (waveBuffer.Buffer != 0 && waveBuffer.BufferSize != 0) + { + ulong bufferOffset = waveBuffer.Buffer + PcmHelper.GetBufferOffset(targetSampleStartOffset, offset, info.ChannelCount); + int bufferSize = PcmHelper.GetBufferSize(targetSampleStartOffset, targetSampleEndOffset, offset, sampleCountToDecode - y) * info.ChannelCount; + + waveBufferPcmFloat = MemoryMarshal.Cast(memoryManager.GetSpan(bufferOffset, bufferSize)); + } + + decodedSampleCount = PcmHelper.Decode(tempSpan, waveBufferPcmFloat, targetSampleStartOffset, targetSampleEndOffset, info.ChannelIndex, info.ChannelCount); + break; + default: + Logger.Error?.Print(LogClass.AudioRenderer, $"Unsupported sample format " + info.SampleFormat); + break; + } + + Debug.Assert(decodedSampleCount <= sampleCountToDecode); + + if (decodedSampleCount < 0) + { + Logger.Warning?.Print(LogClass.AudioRenderer, "Decoding failed, skipping WaveBuffer"); + + voiceState.MarkEndOfBufferWaveBufferProcessing(ref waveBuffer, ref waveBufferIndex, ref waveBufferConsumed, ref playedSampleCount); + decodedSampleCount = 0; + } + + y += decodedSampleCount; + offset += decodedSampleCount; + playedSampleCount += (uint)decodedSampleCount; + + if (offset >= targetWaveBufferSampleCount || decodedSampleCount == 0) + { + offset = 0; + + if (waveBuffer.Looping) + { + voiceState.LoopCount++; + + if (waveBuffer.LoopCount >= 0) + { + if (decodedSampleCount == 0 || voiceState.LoopCount > waveBuffer.LoopCount) + { + voiceState.MarkEndOfBufferWaveBufferProcessing(ref waveBuffer, ref waveBufferIndex, ref waveBufferConsumed, ref playedSampleCount); + } + } + + if (decodedSampleCount == 0) + { + isStarving = true; + break; + } + + if (info.DecodingBehaviour.HasFlag(DecodingBehaviour.PlayedSampleCountResetWhenLooping)) + { + playedSampleCount = 0; + } + } + else + { + voiceState.MarkEndOfBufferWaveBufferProcessing(ref waveBuffer, ref waveBufferIndex, ref waveBufferConsumed, ref playedSampleCount); + } + } + } + + Span outputSpanInt = MemoryMarshal.Cast(outputBuffer.Slice(i)); + + if (info.DecodingBehaviour.HasFlag(DecodingBehaviour.SkipPitchAndSampleRateConversion)) + { + for (int j = 0; j < y; j++) + { + outputBuffer[j] = tempBuffer[j]; + } + } + else + { + Span tempSpan = tempBuffer.Slice(tempBufferIndex + y); + + tempSpan.Slice(0, sampleCountToDecode - y).Fill(0); + + ToFloat(outputBuffer, outputSpanInt, sampleCountToProcess); + + ResamplerHelper.Resample(outputBuffer, tempBuffer, sampleRateRatio, ref fraction, sampleCountToProcess, info.SrcQuality, y != sourceSampleCountToProcess || info.Pitch != 1.0f); + + tempBuffer.Slice(sampleCountToDecode, pitchMaxLength).CopyTo(voiceState.Pitch.AsSpan()); + } + + i += sampleCountToProcess; + } + + Debug.Assert(sourceSampleCountToProcess == i || !isStarving); + + voiceState.WaveBufferConsumed = waveBufferConsumed; + voiceState.Offset = offset; + voiceState.PlayedSampleCount = playedSampleCount; + voiceState.WaveBufferIndex = (uint)waveBufferIndex; + voiceState.Fraction = fraction; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ToFloatAvx(Span output, ReadOnlySpan input, int sampleCount) + { + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input); + Span> outputVec = MemoryMarshal.Cast>(output); + + int sisdStart = inputVec.Length * 8; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Avx.ConvertToVector256Single(inputVec[i]); + } + + for (int i = sisdStart; i < sampleCount; i++) + { + output[i] = input[i]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ToFloatSse2(Span output, ReadOnlySpan input, int sampleCount) + { + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input); + Span> outputVec = MemoryMarshal.Cast>(output); + + int sisdStart = inputVec.Length * 4; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Sse2.ConvertToVector128Single(inputVec[i]); + } + + for (int i = sisdStart; i < sampleCount; i++) + { + output[i] = input[i]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ToFloatAdvSimd(Span output, ReadOnlySpan input, int sampleCount) + { + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input); + Span> outputVec = MemoryMarshal.Cast>(output); + + int sisdStart = inputVec.Length * 4; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = AdvSimd.ConvertToSingle(inputVec[i]); + } + + for (int i = sisdStart; i < sampleCount; i++) + { + output[i] = input[i]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToFloatSlow(Span output, ReadOnlySpan input, int sampleCount) + { + for (int i = 0; i < sampleCount; i++) + { + output[i] = input[i]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToFloat(Span output, ReadOnlySpan input, int sampleCount) + { + if (Avx.IsSupported) + { + ToFloatAvx(output, input, sampleCount); + } + else if (Sse2.IsSupported) + { + ToFloatSse2(output, input, sampleCount); + } + else if (AdvSimd.IsSupported) + { + ToFloatAdvSimd(output, input, sampleCount); + } + else + { + ToFloatSlow(output, input, sampleCount); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToIntAvx(Span output, ReadOnlySpan input, int sampleCount) + { + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input); + Span> outputVec = MemoryMarshal.Cast>(output); + + int sisdStart = inputVec.Length * 8; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Avx.ConvertToVector256Int32(inputVec[i]); + } + + for (int i = sisdStart; i < sampleCount; i++) + { + output[i] = (int)input[i]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToIntSse2(Span output, ReadOnlySpan input, int sampleCount) + { + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input); + Span> outputVec = MemoryMarshal.Cast>(output); + + int sisdStart = inputVec.Length * 4; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = Sse2.ConvertToVector128Int32(inputVec[i]); + } + + for (int i = sisdStart; i < sampleCount; i++) + { + output[i] = (int)input[i]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToIntAdvSimd(Span output, ReadOnlySpan input, int sampleCount) + { + ReadOnlySpan> inputVec = MemoryMarshal.Cast>(input); + Span> outputVec = MemoryMarshal.Cast>(output); + + int sisdStart = inputVec.Length * 4; + + for (int i = 0; i < inputVec.Length; i++) + { + outputVec[i] = AdvSimd.ConvertToInt32RoundToZero(inputVec[i]); + } + + for (int i = sisdStart; i < sampleCount; i++) + { + output[i] = (int)input[i]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToIntSlow(Span output, ReadOnlySpan input, int sampleCount) + { + for (int i = 0; i < sampleCount; i++) + { + output[i] = (int)input[i]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ToInt(Span output, ReadOnlySpan input, int sampleCount) + { + if (Avx.IsSupported) + { + ToIntAvx(output, input, sampleCount); + } + else if (Sse2.IsSupported) + { + ToIntSse2(output, input, sampleCount); + } + else if (AdvSimd.IsSupported) + { + ToIntAdvSimd(output, input, sampleCount); + } + else + { + ToIntSlow(output, input, sampleCount); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void RemapLegacyChannelEffectMappingToChannelResourceMapping(bool isSupported, Span bufferIndices) + { + if (!isSupported && bufferIndices.Length == 6) + { + ushort backLeft = bufferIndices[2]; + ushort backRight = bufferIndices[3]; + ushort frontCenter = bufferIndices[4]; + ushort lowFrequency = bufferIndices[5]; + + bufferIndices[2] = frontCenter; + bufferIndices[3] = lowFrequency; + bufferIndices[4] = backLeft; + bufferIndices[5] = backRight; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void RemapChannelResourceMappingToLegacy(bool isSupported, Span bufferIndices) + { + if (isSupported && bufferIndices.Length == 6) + { + ushort frontCenter = bufferIndices[2]; + ushort lowFrequency = bufferIndices[3]; + ushort backLeft = bufferIndices[4]; + ushort backRight = bufferIndices[5]; + + bufferIndices[2] = backLeft; + bufferIndices[3] = backRight; + bufferIndices[4] = frontCenter; + bufferIndices[5] = lowFrequency; + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Effect/DecayDelay.cs b/src/Ryujinx.Audio/Renderer/Dsp/Effect/DecayDelay.cs new file mode 100644 index 00000000..37e066bf --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Effect/DecayDelay.cs @@ -0,0 +1,52 @@ +namespace Ryujinx.Audio.Renderer.Dsp.Effect +{ + public class DecayDelay : IDelayLine + { + private readonly IDelayLine _delayLine; + + public uint CurrentSampleCount => _delayLine.CurrentSampleCount; + + public uint SampleCountMax => _delayLine.SampleCountMax; + + private float _decayRate; + + public DecayDelay(IDelayLine delayLine) + { + _decayRate = 0.0f; + _delayLine = delayLine; + } + + public void SetDecayRate(float decayRate) + { + _decayRate = decayRate; + } + + public float Update(float value) + { + float delayLineValue = _delayLine.Read(); + float processedValue = value - (_decayRate * delayLineValue); + + return _delayLine.Update(processedValue) + processedValue * _decayRate; + } + + public void SetDelay(float delayTime) + { + _delayLine.SetDelay(delayTime); + } + + public float Read() + { + return _delayLine.Read(); + } + + public float TapUnsafe(uint sampleIndex, int offset) + { + return _delayLine.TapUnsafe(sampleIndex, offset); + } + + public float Tap(uint sampleIndex) + { + return _delayLine.Tap(sampleIndex); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLine.cs b/src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLine.cs new file mode 100644 index 00000000..56890ebe --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLine.cs @@ -0,0 +1,78 @@ +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.Effect +{ + public class DelayLine : IDelayLine + { + private float[] _workBuffer; + private uint _sampleRate; + private uint _currentSampleIndex; + private uint _lastSampleIndex; + + public uint CurrentSampleCount { get; private set; } + public uint SampleCountMax { get; private set; } + + public DelayLine(uint sampleRate, float delayTimeMax) + { + _sampleRate = sampleRate; + SampleCountMax = IDelayLine.GetSampleCount(_sampleRate, delayTimeMax); + _workBuffer = new float[SampleCountMax + 1]; + + SetDelay(delayTimeMax); + } + + private void ConfigureDelay(uint targetSampleCount) + { + CurrentSampleCount = Math.Min(SampleCountMax, targetSampleCount); + _currentSampleIndex = 0; + + if (CurrentSampleCount == 0) + { + _lastSampleIndex = 0; + } + else + { + _lastSampleIndex = CurrentSampleCount - 1; + } + } + + public void SetDelay(float delayTime) + { + ConfigureDelay(IDelayLine.GetSampleCount(_sampleRate, delayTime)); + } + + public float Read() + { + return _workBuffer[_currentSampleIndex]; + } + + public float Update(float value) + { + float output = Read(); + + _workBuffer[_currentSampleIndex++] = value; + + if (_currentSampleIndex >= _lastSampleIndex) + { + _currentSampleIndex = 0; + } + + return output; + } + + public float TapUnsafe(uint sampleIndex, int offset) + { + return IDelayLine.Tap(_workBuffer, (int)_currentSampleIndex, (int)sampleIndex + offset, (int)CurrentSampleCount); + } + + public float Tap(uint sampleIndex) + { + if (sampleIndex >= CurrentSampleCount) + { + sampleIndex = CurrentSampleCount - 1; + } + + return TapUnsafe(sampleIndex, -1); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLineReverb3d.cs b/src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLineReverb3d.cs new file mode 100644 index 00000000..a2ac9d26 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLineReverb3d.cs @@ -0,0 +1,76 @@ +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Dsp.Effect +{ + public class DelayLine3d : IDelayLine + { + private float[] _workBuffer; + private uint _sampleRate; + private uint _currentSampleIndex; + private uint _lastSampleIndex; + + public uint CurrentSampleCount { get; private set; } + public uint SampleCountMax { get; private set; } + + public DelayLine3d(uint sampleRate, float delayTimeMax) + { + _sampleRate = sampleRate; + SampleCountMax = IDelayLine.GetSampleCount(_sampleRate, delayTimeMax); + _workBuffer = new float[SampleCountMax + 1]; + + SetDelay(delayTimeMax); + } + + private void ConfigureDelay(uint targetSampleCount) + { + if (SampleCountMax >= targetSampleCount) + { + CurrentSampleCount = targetSampleCount; + _lastSampleIndex = (_currentSampleIndex + targetSampleCount) % (SampleCountMax + 1); + } + } + + public void SetDelay(float delayTime) + { + ConfigureDelay(IDelayLine.GetSampleCount(_sampleRate, delayTime)); + } + + public float Read() + { + return _workBuffer[_currentSampleIndex]; + } + + public float Update(float value) + { + Debug.Assert(!float.IsNaN(value) && !float.IsInfinity(value)); + + _workBuffer[_lastSampleIndex++] = value; + + float output = Read(); + + _currentSampleIndex++; + + if (_currentSampleIndex >= SampleCountMax) + { + _currentSampleIndex = 0; + } + + if (_lastSampleIndex >= SampleCountMax) + { + _lastSampleIndex = 0; + } + + return output; + } + + public float TapUnsafe(uint sampleIndex, int offset) + { + return IDelayLine.Tap(_workBuffer, (int)_lastSampleIndex, (int)sampleIndex + offset, (int)SampleCountMax + 1); + } + + public float Tap(uint sampleIndex) + { + return TapUnsafe(sampleIndex, -1); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Effect/ExponentialMovingAverage.cs b/src/Ryujinx.Audio/Renderer/Dsp/Effect/ExponentialMovingAverage.cs new file mode 100644 index 00000000..78e46bf9 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Effect/ExponentialMovingAverage.cs @@ -0,0 +1,26 @@ +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Effect +{ + public struct ExponentialMovingAverage + { + private float _mean; + + public ExponentialMovingAverage(float mean) + { + _mean = mean; + } + + public float Read() + { + return _mean; + } + + public float Update(float value, float alpha) + { + _mean += alpha * (value - _mean); + + return _mean; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Effect/IDelayLine.cs b/src/Ryujinx.Audio/Renderer/Dsp/Effect/IDelayLine.cs new file mode 100644 index 00000000..fd902525 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/Effect/IDelayLine.cs @@ -0,0 +1,37 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.Effect +{ + public interface IDelayLine + { + uint CurrentSampleCount { get; } + uint SampleCountMax { get; } + + void SetDelay(float delayTime); + float Read(); + float Update(float value); + + float TapUnsafe(uint sampleIndex, int offset); + float Tap(uint sampleIndex); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Tap(Span workBuffer, int baseIndex, int sampleIndex, int delaySampleCount) + { + int targetIndex = baseIndex - sampleIndex; + + if (targetIndex < 0) + { + targetIndex += delaySampleCount; + } + + return workBuffer[targetIndex]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint GetSampleCount(uint sampleRate, float delayTime) + { + return (uint)MathF.Round(sampleRate * delayTime); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs new file mode 100644 index 00000000..280e47c0 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs @@ -0,0 +1,39 @@ +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class FixedPointHelper + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ToInt(long value, int qBits) + { + return (int)(value >> qBits); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ToFloat(long value, int qBits) + { + return (float)value / (1 << qBits); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ConvertFloat(float value, int qBits) + { + return value / (1 << qBits); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ToFixed(float value, int qBits) + { + return (int)(value * (1 << qBits)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int RoundUpAndToInt(long value, int qBits) + { + int half = 1 << (qBits - 1); + + return ToInt(value + half, qBits); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs new file mode 100644 index 00000000..6645e20a --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs @@ -0,0 +1,115 @@ +using System; +using System.Reflection.Metadata; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class FloatingPointHelper + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MultiplyRoundDown(float a, float b) + { + return RoundDown(a * b); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float RoundDown(float a) + { + return MathF.Round(a, 0); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float RoundUp(float a) + { + return MathF.Round(a); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MultiplyRoundUp(float a, float b) + { + return RoundUp(a * b); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Pow10(float x) + { + // NOTE: Nintendo implementation uses Q15 and a LUT for this, we don't. + // As such, we support the same ranges as Nintendo to avoid unexpected behaviours. + if (x >= 0.0f) + { + return 1.0f; + } + else if (x <= -5.3f) + { + return 0.0f; + } + + return MathF.Pow(10, x); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Log10(float x) + { + // NOTE: Nintendo uses an approximation of log10, we don't. + // As such, we support the same ranges as Nintendo to avoid unexpected behaviours. + return MathF.Pow(10, MathF.Max(x, 1.0e-10f)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float MeanSquare(ReadOnlySpan inputs) + { + float res = 0.0f; + + foreach (float input in inputs) + { + res += (input * input); + } + + res /= inputs.Length; + + return res; + } + + /// + /// Map decibel to linear. + /// + /// The decibel value to convert + /// Converted linear value/returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float DecibelToLinear(float db) + { + return MathF.Pow(10.0f, db / 20.0f); + } + + /// + /// Map decibel to linear in [0, 2] range. + /// + /// The decibel value to convert + /// Converted linear value in [0, 2] range + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float DecibelToLinearExtended(float db) + { + float tmp = MathF.Log2(DecibelToLinear(db)); + + return MathF.Truncate(tmp) + MathF.Pow(2.0f, tmp - MathF.Truncate(tmp)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float DegreesToRadians(float degrees) + { + return degrees * MathF.PI / 180.0f; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Cos(float value) + { + return MathF.Cos(DegreesToRadians(value)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Sin(float value) + { + return MathF.Sin(DegreesToRadians(value)); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs new file mode 100644 index 00000000..0233a8d7 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs @@ -0,0 +1,130 @@ +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class PcmHelper + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetCountToDecode(int startSampleOffset, int endSampleOffset, int offset, int count) + { + return Math.Min(count, endSampleOffset - startSampleOffset - offset); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong GetBufferOffset(int startSampleOffset, int offset, int channelCount) where T : unmanaged + { + return (ulong)(Unsafe.SizeOf() * channelCount * (startSampleOffset + offset)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetBufferSize(int startSampleOffset, int endSampleOffset, int offset, int count) where T : unmanaged + { + return GetCountToDecode(startSampleOffset, endSampleOffset, offset, count) * Unsafe.SizeOf(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float ConvertSampleToPcmFloat(short sample) + { + return (float)sample / short.MaxValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short ConvertSampleToPcmInt16(float sample) + { + return Saturate(sample * short.MaxValue); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertSampleToPcm8(Span output, ReadOnlySpan input) + { + for (int i = 0; i < input.Length; i++) + { + // Output most significant byte + output[i] = (sbyte)(input[i] >> 8); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertSampleToPcm24(Span output, ReadOnlySpan input) + { + for (int i = 0; i < input.Length; i++) + { + output[i * 3 + 2] = (byte)(input[i] >> 8); + output[i * 3 + 1] = (byte)(input[i] & 0xff); + output[i * 3 + 0] = 0; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertSampleToPcm32(Span output, ReadOnlySpan input) + { + for (int i = 0; i < input.Length; i++) + { + output[i] = ((int)input[i]) << 16; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConvertSampleToPcmFloat(Span output, ReadOnlySpan input) + { + for (int i = 0; i < input.Length; i++) + { + output[i] = ConvertSampleToPcmFloat(input[i]); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Decode(Span output, ReadOnlySpan input, int startSampleOffset, int endSampleOffset, int channelIndex, int channelCount) + { + if (input.IsEmpty || endSampleOffset < startSampleOffset) + { + return 0; + } + + int decodedCount = input.Length / channelCount; + + for (int i = 0; i < decodedCount; i++) + { + output[i] = input[i * channelCount + channelIndex]; + } + + return decodedCount; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Decode(Span output, ReadOnlySpan input, int startSampleOffset, int endSampleOffset, int channelIndex, int channelCount) + { + if (input.IsEmpty || endSampleOffset < startSampleOffset) + { + return 0; + } + + int decodedCount = input.Length / channelCount; + + for (int i = 0; i < decodedCount; i++) + { + output[i] = ConvertSampleToPcmInt16(input[i * channelCount + channelIndex]); + } + + return decodedCount; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short Saturate(float value) + { + if (value > short.MaxValue) + { + return short.MaxValue; + } + + if (value < short.MinValue) + { + return short.MinValue; + } + + return (short)value; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs new file mode 100644 index 00000000..7873c4d2 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs @@ -0,0 +1,604 @@ +using System; +using System.Linq; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public static class ResamplerHelper + { + #region "Default Quality Lookup Tables" + private static short[] _normalCurveLut0 = new short[] + { + 6600, 19426, 6722, 3, 6479, 19424, 6845, 9, 6359, 19419, 6968, 15, 6239, 19412, 7093, 22, + 6121, 19403, 7219, 28, 6004, 19391, 7345, 34, 5888, 19377, 7472, 41, 5773, 19361, 7600, 48, + 5659, 19342, 7728, 55, 5546, 19321, 7857, 62, 5434, 19298, 7987, 69, 5323, 19273, 8118, 77, + 5213, 19245, 8249, 84, 5104, 19215, 8381, 92, 4997, 19183, 8513, 101, 4890, 19148, 8646, 109, + 4785, 19112, 8780, 118, 4681, 19073, 8914, 127, 4579, 19031, 9048, 137, 4477, 18988, 9183, 147, + 4377, 18942, 9318, 157, 4277, 18895, 9454, 168, 4179, 18845, 9590, 179, 4083, 18793, 9726, 190, + 3987, 18738, 9863, 202, 3893, 18682, 10000, 215, 3800, 18624, 10137, 228, 3709, 18563, 10274, 241, + 3618, 18500, 10411, 255, 3529, 18436, 10549, 270, 3441, 18369, 10687, 285, 3355, 18300, 10824, 300, + 3269, 18230, 10962, 317, 3186, 18157, 11100, 334, 3103, 18082, 11238, 351, 3022, 18006, 11375, 369, + 2942, 17927, 11513, 388, 2863, 17847, 11650, 408, 2785, 17765, 11788, 428, 2709, 17681, 11925, 449, + 2635, 17595, 12062, 471, 2561, 17507, 12198, 494, 2489, 17418, 12334, 517, 2418, 17327, 12470, 541, + 2348, 17234, 12606, 566, 2280, 17140, 12741, 592, 2213, 17044, 12876, 619, 2147, 16946, 13010, 647, + 2083, 16846, 13144, 675, 2020, 16745, 13277, 704, 1958, 16643, 13409, 735, 1897, 16539, 13541, 766, + 1838, 16434, 13673, 798, 1780, 16327, 13803, 832, 1723, 16218, 13933, 866, 1667, 16109, 14062, 901, + 1613, 15998, 14191, 937, 1560, 15885, 14318, 975, 1508, 15772, 14445, 1013, 1457, 15657, 14571, 1052, + 1407, 15540, 14695, 1093, 1359, 15423, 14819, 1134, 1312, 15304, 14942, 1177, 1266, 15185, 15064, 1221, + 1221, 15064, 15185, 1266, 1177, 14942, 15304, 1312, 1134, 14819, 15423, 1359, 1093, 14695, 15540, 1407, + 1052, 14571, 15657, 1457, 1013, 14445, 15772, 1508, 975, 14318, 15885, 1560, 937, 14191, 15998, 1613, + 901, 14062, 16109, 1667, 866, 13933, 16218, 1723, 832, 13803, 16327, 1780, 798, 13673, 16434, 1838, + 766, 13541, 16539, 1897, 735, 13409, 16643, 1958, 704, 13277, 16745, 2020, 675, 13144, 16846, 2083, + 647, 13010, 16946, 2147, 619, 12876, 17044, 2213, 592, 12741, 17140, 2280, 566, 12606, 17234, 2348, + 541, 12470, 17327, 2418, 517, 12334, 17418, 2489, 494, 12198, 17507, 2561, 471, 12062, 17595, 2635, + 449, 11925, 17681, 2709, 428, 11788, 17765, 2785, 408, 11650, 17847, 2863, 388, 11513, 17927, 2942, + 369, 11375, 18006, 3022, 351, 11238, 18082, 3103, 334, 11100, 18157, 3186, 317, 10962, 18230, 3269, + 300, 10824, 18300, 3355, 285, 10687, 18369, 3441, 270, 10549, 18436, 3529, 255, 10411, 18500, 3618, + 241, 10274, 18563, 3709, 228, 10137, 18624, 3800, 215, 10000, 18682, 3893, 202, 9863, 18738, 3987, + 190, 9726, 18793, 4083, 179, 9590, 18845, 4179, 168, 9454, 18895, 4277, 157, 9318, 18942, 4377, + 147, 9183, 18988, 4477, 137, 9048, 19031, 4579, 127, 8914, 19073, 4681, 118, 8780, 19112, 4785, + 109, 8646, 19148, 4890, 101, 8513, 19183, 4997, 92, 8381, 19215, 5104, 84, 8249, 19245, 5213, + 77, 8118, 19273, 5323, 69, 7987, 19298, 5434, 62, 7857, 19321, 5546, 55, 7728, 19342, 5659, + 48, 7600, 19361, 5773, 41, 7472, 19377, 5888, 34, 7345, 19391, 6004, 28, 7219, 19403, 6121, + 22, 7093, 19412, 6239, 15, 6968, 19419, 6359, 9, 6845, 19424, 6479, 3, 6722, 19426, 6600 + }; + + private static short[] _normalCurveLut1 = new short[] + { + -68, 32639, 69, -5, -200, 32630, 212, -15, -328, 32613, 359, -26, -450, 32586, 512, -36, + -568, 32551, 669, -47, -680, 32507, 832, -58, -788, 32454, 1000, -69, -891, 32393, 1174, -80, + -990, 32323, 1352, -92, -1084, 32244, 1536, -103, -1173, 32157, 1724, -115, -1258, 32061, 1919, -128, + -1338, 31956, 2118, -140, -1414, 31844, 2322, -153, -1486, 31723, 2532, -167, -1554, 31593, 2747, -180, + -1617, 31456, 2967, -194, -1676, 31310, 3192, -209, -1732, 31157, 3422, -224, -1783, 30995, 3657, -240, + -1830, 30826, 3897, -256, -1874, 30649, 4143, -272, -1914, 30464, 4393, -289, -1951, 30272, 4648, -307, + -1984, 30072, 4908, -325, -2014, 29866, 5172, -343, -2040, 29652, 5442, -362, -2063, 29431, 5716, -382, + -2083, 29203, 5994, -403, -2100, 28968, 6277, -424, -2114, 28727, 6565, -445, -2125, 28480, 6857, -468, + -2133, 28226, 7153, -490, -2139, 27966, 7453, -514, -2142, 27700, 7758, -538, -2142, 27428, 8066, -563, + -2141, 27151, 8378, -588, -2136, 26867, 8694, -614, -2130, 26579, 9013, -641, -2121, 26285, 9336, -668, + -2111, 25987, 9663, -696, -2098, 25683, 9993, -724, -2084, 25375, 10326, -753, -2067, 25063, 10662, -783, + -2049, 24746, 11000, -813, -2030, 24425, 11342, -844, -2009, 24100, 11686, -875, -1986, 23771, 12033, -907, + -1962, 23438, 12382, -939, -1937, 23103, 12733, -972, -1911, 22764, 13086, -1005, -1883, 22422, 13441, -1039, + -1855, 22077, 13798, -1072, -1825, 21729, 14156, -1107, -1795, 21380, 14516, -1141, -1764, 21027, 14877, -1176, + -1732, 20673, 15239, -1211, -1700, 20317, 15602, -1246, -1667, 19959, 15965, -1282, -1633, 19600, 16329, -1317, + -1599, 19239, 16694, -1353, -1564, 18878, 17058, -1388, -1530, 18515, 17423, -1424, -1495, 18151, 17787, -1459, + -1459, 17787, 18151, -1495, -1424, 17423, 18515, -1530, -1388, 17058, 18878, -1564, -1353, 16694, 19239, -1599, + -1317, 16329, 19600, -1633, -1282, 15965, 19959, -1667, -1246, 15602, 20317, -1700, -1211, 15239, 20673, -1732, + -1176, 14877, 21027, -1764, -1141, 14516, 21380, -1795, -1107, 14156, 21729, -1825, -1072, 13798, 22077, -1855, + -1039, 13441, 22422, -1883, -1005, 13086, 22764, -1911, -972, 12733, 23103, -1937, -939, 12382, 23438, -1962, + -907, 12033, 23771, -1986, -875, 11686, 24100, -2009, -844, 11342, 24425, -2030, -813, 11000, 24746, -2049, + -783, 10662, 25063, -2067, -753, 10326, 25375, -2084, -724, 9993, 25683, -2098, -696, 9663, 25987, -2111, + -668, 9336, 26285, -2121, -641, 9013, 26579, -2130, -614, 8694, 26867, -2136, -588, 8378, 27151, -2141, + -563, 8066, 27428, -2142, -538, 7758, 27700, -2142, -514, 7453, 27966, -2139, -490, 7153, 28226, -2133, + -468, 6857, 28480, -2125, -445, 6565, 28727, -2114, -424, 6277, 28968, -2100, -403, 5994, 29203, -2083, + -382, 5716, 29431, -2063, -362, 5442, 29652, -2040, -343, 5172, 29866, -2014, -325, 4908, 30072, -1984, + -307, 4648, 30272, -1951, -289, 4393, 30464, -1914, -272, 4143, 30649, -1874, -256, 3897, 30826, -1830, + -240, 3657, 30995, -1783, -224, 3422, 31157, -1732, -209, 3192, 31310, -1676, -194, 2967, 31456, -1617, + -180, 2747, 31593, -1554, -167, 2532, 31723, -1486, -153, 2322, 31844, -1414, -140, 2118, 31956, -1338, + -128, 1919, 32061, -1258, -115, 1724, 32157, -1173, -103, 1536, 32244, -1084, -92, 1352, 32323, -990, + -80, 1174, 32393, -891, -69, 1000, 32454, -788, -58, 832, 32507, -680, -47, 669, 32551, -568, + -36, 512, 32586, -450, -26, 359, 32613, -328, -15, 212, 32630, -200, -5, 69, 32639, -68 + }; + + private static short[] _normalCurveLut2 = new short[] + { + 3195, 26287, 3329, -32, 3064, 26281, 3467, -34, 2936, 26270, 3608, -38, 2811, 26253, 3751, -42, + 2688, 26230, 3897, -46, 2568, 26202, 4046, -50, 2451, 26169, 4199, -54, 2338, 26130, 4354, -58, + 2227, 26085, 4512, -63, 2120, 26035, 4673, -67, 2015, 25980, 4837, -72, 1912, 25919, 5004, -76, + 1813, 25852, 5174, -81, 1716, 25780, 5347, -87, 1622, 25704, 5522, -92, 1531, 25621, 5701, -98, + 1442, 25533, 5882, -103, 1357, 25440, 6066, -109, 1274, 25342, 6253, -115, 1193, 25239, 6442, -121, + 1115, 25131, 6635, -127, 1040, 25018, 6830, -133, 967, 24899, 7027, -140, 897, 24776, 7227, -146, + 829, 24648, 7430, -153, 764, 24516, 7635, -159, 701, 24379, 7842, -166, 641, 24237, 8052, -174, + 583, 24091, 8264, -181, 526, 23940, 8478, -187, 472, 23785, 8695, -194, 420, 23626, 8914, -202, + 371, 23462, 9135, -209, 324, 23295, 9358, -215, 279, 23123, 9583, -222, 236, 22948, 9809, -230, + 194, 22769, 10038, -237, 154, 22586, 10269, -243, 117, 22399, 10501, -250, 81, 22208, 10735, -258, + 47, 22015, 10970, -265, 15, 21818, 11206, -271, -16, 21618, 11444, -277, -44, 21415, 11684, -283, + -71, 21208, 11924, -290, -97, 20999, 12166, -296, -121, 20786, 12409, -302, -143, 20571, 12653, -306, + -163, 20354, 12898, -311, -183, 20134, 13143, -316, -201, 19911, 13389, -321, -218, 19686, 13635, -325, + -234, 19459, 13882, -328, -248, 19230, 14130, -332, -261, 18998, 14377, -335, -273, 18765, 14625, -337, + -284, 18531, 14873, -339, -294, 18295, 15121, -341, -302, 18057, 15369, -341, -310, 17817, 15617, -341, + -317, 17577, 15864, -340, -323, 17335, 16111, -340, -328, 17092, 16357, -338, -332, 16848, 16603, -336, + -336, 16603, 16848, -332, -338, 16357, 17092, -328, -340, 16111, 17335, -323, -340, 15864, 17577, -317, + -341, 15617, 17817, -310, -341, 15369, 18057, -302, -341, 15121, 18295, -294, -339, 14873, 18531, -284, + -337, 14625, 18765, -273, -335, 14377, 18998, -261, -332, 14130, 19230, -248, -328, 13882, 19459, -234, + -325, 13635, 19686, -218, -321, 13389, 19911, -201, -316, 13143, 20134, -183, -311, 12898, 20354, -163, + -306, 12653, 20571, -143, -302, 12409, 20786, -121, -296, 12166, 20999, -97, -290, 11924, 21208, -71, + -283, 11684, 21415, -44, -277, 11444, 21618, -16, -271, 11206, 21818, 15, -265, 10970, 22015, 47, + -258, 10735, 22208, 81, -250, 10501, 22399, 117, -243, 10269, 22586, 154, -237, 10038, 22769, 194, + -230, 9809, 22948, 236, -222, 9583, 23123, 279, -215, 9358, 23295, 324, -209, 9135, 23462, 371, + -202, 8914, 23626, 420, -194, 8695, 23785, 472, -187, 8478, 23940, 526, -181, 8264, 24091, 583, + -174, 8052, 24237, 641, -166, 7842, 24379, 701, -159, 7635, 24516, 764, -153, 7430, 24648, 829, + -146, 7227, 24776, 897, -140, 7027, 24899, 967, -133, 6830, 25018, 1040, -127, 6635, 25131, 1115, + -121, 6442, 25239, 1193, -115, 6253, 25342, 1274, -109, 6066, 25440, 1357, -103, 5882, 25533, 1442, + -98, 5701, 25621, 1531, -92, 5522, 25704, 1622, -87, 5347, 25780, 1716, -81, 5174, 25852, 1813, + -76, 5004, 25919, 1912, -72, 4837, 25980, 2015, -67, 4673, 26035, 2120, -63, 4512, 26085, 2227, + -58, 4354, 26130, 2338, -54, 4199, 26169, 2451, -50, 4046, 26202, 2568, -46, 3897, 26230, 2688, + -42, 3751, 26253, 2811, -38, 3608, 26270, 2936, -34, 3467, 26281, 3064, -32, 3329, 26287, 3195 + }; + #endregion + + #region "High Quality Lookup Tables" + private static short[] _highCurveLut0 = new short[] + { + -582, -23, 8740, 16386, 8833, 8, -590, 0, -573, -54, 8647, 16385, 8925, 40, -598, -1, + -565, -84, 8555, 16383, 9018, 72, -606, -1, -557, -113, 8462, 16379, 9110, 105, -614, -2, + -549, -142, 8370, 16375, 9203, 139, -622, -2, -541, -170, 8277, 16369, 9295, 173, -630, -3, + -533, -198, 8185, 16362, 9387, 208, -638, -4, -525, -225, 8093, 16354, 9480, 244, -646, -5, + -516, -251, 8000, 16344, 9572, 280, -654, -5, -508, -277, 7908, 16334, 9664, 317, -662, -6, + -500, -302, 7816, 16322, 9756, 355, -670, -7, -492, -327, 7724, 16310, 9847, 393, -678, -8, + -484, -351, 7632, 16296, 9939, 432, -686, -9, -476, -374, 7540, 16281, 10030, 471, -694, -10, + -468, -397, 7449, 16265, 10121, 511, -702, -11, -460, -419, 7357, 16247, 10212, 552, -709, -13, + -452, -441, 7266, 16229, 10303, 593, -717, -14, -445, -462, 7175, 16209, 10394, 635, -724, -15, + -437, -483, 7084, 16189, 10484, 678, -732, -16, -429, -503, 6994, 16167, 10574, 722, -739, -18, + -421, -523, 6903, 16144, 10664, 766, -747, -19, -414, -542, 6813, 16120, 10754, 810, -754, -21, + -406, -560, 6723, 16095, 10843, 856, -761, -22, -398, -578, 6633, 16068, 10932, 902, -768, -24, + -391, -596, 6544, 16041, 11021, 949, -775, -26, -383, -612, 6454, 16012, 11109, 996, -782, -27, + -376, -629, 6366, 15983, 11197, 1044, -789, -29, -368, -645, 6277, 15952, 11285, 1093, -796, -31, + -361, -660, 6189, 15920, 11372, 1142, -802, -33, -354, -675, 6100, 15887, 11459, 1192, -809, -35, + -347, -689, 6013, 15853, 11546, 1243, -815, -37, -339, -703, 5925, 15818, 11632, 1294, -821, -39, + -332, -717, 5838, 15782, 11718, 1346, -827, -41, -325, -730, 5751, 15745, 11803, 1399, -833, -43, + -318, -742, 5665, 15707, 11888, 1452, -839, -46, -312, -754, 5579, 15668, 11973, 1506, -845, -48, + -305, -766, 5493, 15627, 12057, 1561, -850, -50, -298, -777, 5408, 15586, 12140, 1616, -855, -53, + -291, -787, 5323, 15544, 12224, 1672, -861, -56, -285, -798, 5239, 15500, 12306, 1729, -866, -58, + -278, -807, 5155, 15456, 12388, 1786, -871, -61, -272, -817, 5071, 15410, 12470, 1844, -875, -64, + -265, -826, 4988, 15364, 12551, 1902, -880, -67, -259, -834, 4905, 15317, 12631, 1962, -884, -70, + -253, -842, 4823, 15268, 12711, 2022, -888, -73, -247, -850, 4741, 15219, 12790, 2082, -892, -76, + -241, -857, 4659, 15168, 12869, 2143, -896, -79, -235, -864, 4578, 15117, 12947, 2205, -899, -82, + -229, -870, 4498, 15065, 13025, 2267, -903, -85, -223, -876, 4417, 15012, 13102, 2331, -906, -89, + -217, -882, 4338, 14958, 13178, 2394, -909, -92, -211, -887, 4259, 14903, 13254, 2459, -911, -96, + -206, -892, 4180, 14847, 13329, 2523, -914, -100, -200, -896, 4102, 14790, 13403, 2589, -916, -103, + -195, -900, 4024, 14732, 13477, 2655, -918, -107, -190, -904, 3947, 14673, 13550, 2722, -919, -111, + -184, -908, 3871, 14614, 13622, 2789, -921, -115, -179, -911, 3795, 14553, 13693, 2857, -922, -119, + -174, -913, 3719, 14492, 13764, 2926, -923, -123, -169, -916, 3644, 14430, 13834, 2995, -923, -127, + -164, -918, 3570, 14367, 13904, 3065, -924, -132, -159, -920, 3496, 14303, 13972, 3136, -924, -136, + -154, -921, 3423, 14239, 14040, 3207, -924, -140, -150, -922, 3350, 14173, 14107, 3278, -923, -145, + -145, -923, 3278, 14107, 14173, 3350, -922, -150, -140, -924, 3207, 14040, 14239, 3423, -921, -154, + -136, -924, 3136, 13972, 14303, 3496, -920, -159, -132, -924, 3065, 13904, 14367, 3570, -918, -164, + -127, -923, 2995, 13834, 14430, 3644, -916, -169, -123, -923, 2926, 13764, 14492, 3719, -913, -174, + -119, -922, 2857, 13693, 14553, 3795, -911, -179, -115, -921, 2789, 13622, 14614, 3871, -908, -184, + -111, -919, 2722, 13550, 14673, 3947, -904, -190, -107, -918, 2655, 13477, 14732, 4024, -900, -195, + -103, -916, 2589, 13403, 14790, 4102, -896, -200, -100, -914, 2523, 13329, 14847, 4180, -892, -206, + -96, -911, 2459, 13254, 14903, 4259, -887, -211, -92, -909, 2394, 13178, 14958, 4338, -882, -217, + -89, -906, 2331, 13102, 15012, 4417, -876, -223, -85, -903, 2267, 13025, 15065, 4498, -870, -229, + -82, -899, 2205, 12947, 15117, 4578, -864, -235, -79, -896, 2143, 12869, 15168, 4659, -857, -241, + -76, -892, 2082, 12790, 15219, 4741, -850, -247, -73, -888, 2022, 12711, 15268, 4823, -842, -253, + -70, -884, 1962, 12631, 15317, 4905, -834, -259, -67, -880, 1902, 12551, 15364, 4988, -826, -265, + -64, -875, 1844, 12470, 15410, 5071, -817, -272, -61, -871, 1786, 12388, 15456, 5155, -807, -278, + -58, -866, 1729, 12306, 15500, 5239, -798, -285, -56, -861, 1672, 12224, 15544, 5323, -787, -291, + -53, -855, 1616, 12140, 15586, 5408, -777, -298, -50, -850, 1561, 12057, 15627, 5493, -766, -305, + -48, -845, 1506, 11973, 15668, 5579, -754, -312, -46, -839, 1452, 11888, 15707, 5665, -742, -318, + -43, -833, 1399, 11803, 15745, 5751, -730, -325, -41, -827, 1346, 11718, 15782, 5838, -717, -332, + -39, -821, 1294, 11632, 15818, 5925, -703, -339, -37, -815, 1243, 11546, 15853, 6013, -689, -347, + -35, -809, 1192, 11459, 15887, 6100, -675, -354, -33, -802, 1142, 11372, 15920, 6189, -660, -361, + -31, -796, 1093, 11285, 15952, 6277, -645, -368, -29, -789, 1044, 11197, 15983, 6366, -629, -376, + -27, -782, 996, 11109, 16012, 6454, -612, -383, -26, -775, 949, 11021, 16041, 6544, -596, -391, + -24, -768, 902, 10932, 16068, 6633, -578, -398, -22, -761, 856, 10843, 16095, 6723, -560, -406, + -21, -754, 810, 10754, 16120, 6813, -542, -414, -19, -747, 766, 10664, 16144, 6903, -523, -421, + -18, -739, 722, 10574, 16167, 6994, -503, -429, -16, -732, 678, 10484, 16189, 7084, -483, -437, + -15, -724, 635, 10394, 16209, 7175, -462, -445, -14, -717, 593, 10303, 16229, 7266, -441, -452, + -13, -709, 552, 10212, 16247, 7357, -419, -460, -11, -702, 511, 10121, 16265, 7449, -397, -468, + -10, -694, 471, 10030, 16281, 7540, -374, -476, -9, -686, 432, 9939, 16296, 7632, -351, -484, + -8, -678, 393, 9847, 16310, 7724, -327, -492, -7, -670, 355, 9756, 16322, 7816, -302, -500, + -6, -662, 317, 9664, 16334, 7908, -277, -508, -5, -654, 280, 9572, 16344, 8000, -251, -516, + -5, -646, 244, 9480, 16354, 8093, -225, -525, -4, -638, 208, 9387, 16362, 8185, -198, -533, + -3, -630, 173, 9295, 16369, 8277, -170, -541, -2, -622, 139, 9203, 16375, 8370, -142, -549, + -2, -614, 105, 9110, 16379, 8462, -113, -557, -1, -606, 72, 9018, 16383, 8555, -84, -565, + -1, -598, 40, 8925, 16385, 8647, -54, -573, 0, -590, 8, 8833, 16386, 8740, -23, -582, + }; + + private static short[] _highCurveLut1 = new short[] + { + -12, 47, -134, 32767, 81, -16, 2, 0, -26, 108, -345, 32760, 301, -79, 17, -1, + -40, 168, -552, 32745, 526, -144, 32, -2, -53, 226, -753, 32723, 755, -210, 47, -3, + -66, 284, -950, 32694, 989, -277, 63, -5, -78, 340, -1143, 32658, 1226, -346, 79, -6, + -90, 394, -1331, 32615, 1469, -415, 96, -8, -101, 447, -1514, 32564, 1715, -486, 113, -9, + -112, 499, -1692, 32506, 1966, -557, 130, -11, -123, 550, -1865, 32441, 2221, -630, 148, -13, + -133, 599, -2034, 32369, 2480, -703, 166, -14, -143, 646, -2198, 32290, 2743, -778, 185, -16, + -152, 693, -2357, 32204, 3010, -853, 204, -18, -162, 738, -2512, 32110, 3281, -929, 223, -20, + -170, 781, -2662, 32010, 3555, -1007, 242, -23, -178, 823, -2807, 31903, 3834, -1084, 262, -25, + -186, 864, -2947, 31789, 4116, -1163, 282, -27, -194, 903, -3082, 31668, 4403, -1242, 303, -30, + -201, 940, -3213, 31540, 4692, -1322, 323, -32, -207, 977, -3339, 31406, 4985, -1403, 344, -35, + -214, 1011, -3460, 31265, 5282, -1484, 365, -37, -220, 1045, -3577, 31117, 5582, -1566, 387, -40, + -225, 1077, -3688, 30963, 5885, -1648, 409, -43, -230, 1107, -3796, 30802, 6191, -1730, 431, -46, + -235, 1136, -3898, 30635, 6501, -1813, 453, -49, -240, 1164, -3996, 30462, 6813, -1896, 475, -52, + -244, 1190, -4089, 30282, 7128, -1980, 498, -55, -247, 1215, -4178, 30097, 7446, -2064, 520, -58, + -251, 1239, -4262, 29905, 7767, -2148, 543, -62, -254, 1261, -4342, 29707, 8091, -2231, 566, -65, + -257, 1281, -4417, 29503, 8416, -2315, 589, -69, -259, 1301, -4488, 29293, 8745, -2399, 613, -72, + -261, 1319, -4555, 29078, 9075, -2483, 636, -76, -263, 1336, -4617, 28857, 9408, -2567, 659, -80, + -265, 1351, -4674, 28631, 9743, -2651, 683, -83, -266, 1365, -4728, 28399, 10080, -2734, 706, -87, + -267, 1378, -4777, 28161, 10418, -2817, 730, -91, -267, 1389, -4822, 27919, 10759, -2899, 753, -95, + -268, 1400, -4863, 27671, 11100, -2981, 777, -99, -268, 1409, -4900, 27418, 11444, -3063, 800, -103, + -268, 1416, -4933, 27161, 11789, -3144, 824, -107, -267, 1423, -4962, 26898, 12135, -3224, 847, -112, + -267, 1428, -4987, 26631, 12482, -3303, 870, -116, -266, 1433, -5008, 26359, 12830, -3382, 893, -120, + -265, 1436, -5026, 26083, 13179, -3460, 916, -125, -264, 1438, -5039, 25802, 13529, -3537, 939, -129, + -262, 1438, -5049, 25517, 13880, -3613, 962, -133, -260, 1438, -5055, 25228, 14231, -3687, 984, -138, + -258, 1437, -5058, 24935, 14582, -3761, 1006, -142, -256, 1435, -5058, 24639, 14934, -3833, 1028, -147, + -254, 1431, -5053, 24338, 15286, -3904, 1049, -151, -252, 1427, -5046, 24034, 15638, -3974, 1071, -155, + -249, 1422, -5035, 23726, 15989, -4042, 1091, -160, -246, 1416, -5021, 23415, 16341, -4109, 1112, -164, + -243, 1408, -5004, 23101, 16691, -4174, 1132, -169, -240, 1400, -4984, 22783, 17042, -4237, 1152, -173, + -237, 1392, -4960, 22463, 17392, -4299, 1171, -178, -234, 1382, -4934, 22140, 17740, -4358, 1190, -182, + -230, 1371, -4905, 21814, 18088, -4416, 1209, -186, -227, 1360, -4873, 21485, 18435, -4472, 1226, -191, + -223, 1348, -4839, 21154, 18781, -4526, 1244, -195, -219, 1335, -4801, 20821, 19125, -4578, 1260, -199, + -215, 1321, -4761, 20486, 19468, -4627, 1277, -203, -211, 1307, -4719, 20148, 19809, -4674, 1292, -207, + -207, 1292, -4674, 19809, 20148, -4719, 1307, -211, -203, 1277, -4627, 19468, 20486, -4761, 1321, -215, + -199, 1260, -4578, 19125, 20821, -4801, 1335, -219, -195, 1244, -4526, 18781, 21154, -4839, 1348, -223, + -191, 1226, -4472, 18435, 21485, -4873, 1360, -227, -186, 1209, -4416, 18088, 21814, -4905, 1371, -230, + -182, 1190, -4358, 17740, 22140, -4934, 1382, -234, -178, 1171, -4299, 17392, 22463, -4960, 1392, -237, + -173, 1152, -4237, 17042, 22783, -4984, 1400, -240, -169, 1132, -4174, 16691, 23101, -5004, 1408, -243, + -164, 1112, -4109, 16341, 23415, -5021, 1416, -246, -160, 1091, -4042, 15989, 23726, -5035, 1422, -249, + -155, 1071, -3974, 15638, 24034, -5046, 1427, -252, -151, 1049, -3904, 15286, 24338, -5053, 1431, -254, + -147, 1028, -3833, 14934, 24639, -5058, 1435, -256, -142, 1006, -3761, 14582, 24935, -5058, 1437, -258, + -138, 984, -3687, 14231, 25228, -5055, 1438, -260, -133, 962, -3613, 13880, 25517, -5049, 1438, -262, + -129, 939, -3537, 13529, 25802, -5039, 1438, -264, -125, 916, -3460, 13179, 26083, -5026, 1436, -265, + -120, 893, -3382, 12830, 26359, -5008, 1433, -266, -116, 870, -3303, 12482, 26631, -4987, 1428, -267, + -112, 847, -3224, 12135, 26898, -4962, 1423, -267, -107, 824, -3144, 11789, 27161, -4933, 1416, -268, + -103, 800, -3063, 11444, 27418, -4900, 1409, -268, -99, 777, -2981, 11100, 27671, -4863, 1400, -268, + -95, 753, -2899, 10759, 27919, -4822, 1389, -267, -91, 730, -2817, 10418, 28161, -4777, 1378, -267, + -87, 706, -2734, 10080, 28399, -4728, 1365, -266, -83, 683, -2651, 9743, 28631, -4674, 1351, -265, + -80, 659, -2567, 9408, 28857, -4617, 1336, -263, -76, 636, -2483, 9075, 29078, -4555, 1319, -261, + -72, 613, -2399, 8745, 29293, -4488, 1301, -259, -69, 589, -2315, 8416, 29503, -4417, 1281, -257, + -65, 566, -2231, 8091, 29707, -4342, 1261, -254, -62, 543, -2148, 7767, 29905, -4262, 1239, -251, + -58, 520, -2064, 7446, 30097, -4178, 1215, -247, -55, 498, -1980, 7128, 30282, -4089, 1190, -244, + -52, 475, -1896, 6813, 30462, -3996, 1164, -240, -49, 453, -1813, 6501, 30635, -3898, 1136, -235, + -46, 431, -1730, 6191, 30802, -3796, 1107, -230, -43, 409, -1648, 5885, 30963, -3688, 1077, -225, + -40, 387, -1566, 5582, 31117, -3577, 1045, -220, -37, 365, -1484, 5282, 31265, -3460, 1011, -214, + -35, 344, -1403, 4985, 31406, -3339, 977, -207, -32, 323, -1322, 4692, 31540, -3213, 940, -201, + -30, 303, -1242, 4403, 31668, -3082, 903, -194, -27, 282, -1163, 4116, 31789, -2947, 864, -186, + -25, 262, -1084, 3834, 31903, -2807, 823, -178, -23, 242, -1007, 3555, 32010, -2662, 781, -170, + -20, 223, -929, 3281, 32110, -2512, 738, -162, -18, 204, -853, 3010, 32204, -2357, 693, -152, + -16, 185, -778, 2743, 32290, -2198, 646, -143, -14, 166, -703, 2480, 32369, -2034, 599, -133, + -13, 148, -630, 2221, 32441, -1865, 550, -123, -11, 130, -557, 1966, 32506, -1692, 499, -112, + -9, 113, -486, 1715, 32564, -1514, 447, -101, -8, 96, -415, 1469, 32615, -1331, 394, -90, + -6, 79, -346, 1226, 32658, -1143, 340, -78, -5, 63, -277, 989, 32694, -950, 284, -66, + -3, 47, -210, 755, 32723, -753, 226, -53, -2, 32, -144, 526, 32745, -552, 168, -40, + -1, 17, -79, 301, 32760, -345, 108, -26, 0, 2, -16, 81, 32767, -134, 47, -12, + }; + + private static short[] _highCurveLut2 = new short[] + { + 418, -2538, 6118, 24615, 6298, -2563, 417, 0, 420, -2512, 5939, 24611, 6479, -2588, 415, 1, + 421, -2485, 5761, 24605, 6662, -2612, 412, 2, 422, -2458, 5585, 24595, 6846, -2635, 409, 3, + 423, -2430, 5410, 24582, 7030, -2658, 406, 4, 423, -2402, 5236, 24565, 7216, -2680, 403, 5, + 423, -2373, 5064, 24546, 7403, -2701, 399, 6, 423, -2343, 4893, 24523, 7591, -2721, 395, 7, + 423, -2313, 4724, 24496, 7780, -2741, 391, 8, 422, -2283, 4556, 24467, 7970, -2759, 386, 9, + 421, -2252, 4390, 24434, 8161, -2777, 381, 11, 420, -2221, 4225, 24398, 8353, -2794, 376, 12, + 419, -2190, 4062, 24359, 8545, -2810, 370, 14, 418, -2158, 3900, 24316, 8739, -2825, 364, 15, + 416, -2126, 3740, 24271, 8933, -2839, 358, 17, 414, -2093, 3582, 24222, 9127, -2851, 351, 19, + 412, -2060, 3425, 24170, 9323, -2863, 344, 21, 410, -2027, 3270, 24115, 9519, -2874, 336, 22, + 407, -1993, 3117, 24056, 9715, -2884, 328, 24, 404, -1960, 2966, 23995, 9912, -2893, 319, 26, + 402, -1926, 2816, 23930, 10110, -2900, 311, 29, 398, -1892, 2668, 23863, 10308, -2907, 301, 31, + 395, -1858, 2522, 23792, 10506, -2912, 292, 33, 392, -1823, 2378, 23718, 10705, -2916, 282, 35, + 389, -1789, 2235, 23641, 10904, -2919, 271, 38, 385, -1754, 2095, 23561, 11103, -2920, 261, 40, + 381, -1719, 1956, 23478, 11303, -2921, 249, 43, 377, -1684, 1819, 23393, 11502, -2920, 238, 45, + 373, -1649, 1684, 23304, 11702, -2917, 225, 48, 369, -1615, 1551, 23212, 11902, -2914, 213, 51, + 365, -1580, 1420, 23118, 12102, -2909, 200, 54, 361, -1545, 1291, 23020, 12302, -2902, 186, 57, + 356, -1510, 1163, 22920, 12502, -2895, 173, 60, 352, -1475, 1038, 22817, 12702, -2885, 158, 63, + 347, -1440, 915, 22711, 12901, -2875, 143, 66, 342, -1405, 793, 22602, 13101, -2863, 128, 69, + 338, -1370, 674, 22491, 13300, -2849, 113, 73, 333, -1335, 557, 22377, 13499, -2834, 97, 76, + 328, -1301, 441, 22260, 13698, -2817, 80, 80, 323, -1266, 328, 22141, 13896, -2799, 63, 83, + 318, -1232, 217, 22019, 14094, -2779, 46, 87, 313, -1197, 107, 21894, 14291, -2758, 28, 91, + 307, -1163, 0, 21767, 14488, -2735, 9, 95, 302, -1129, -105, 21637, 14684, -2710, -9, 98, + 297, -1096, -208, 21506, 14879, -2684, -29, 102, 292, -1062, -310, 21371, 15074, -2656, -48, 106, + 286, -1029, -409, 21234, 15268, -2626, -69, 111, 281, -996, -506, 21095, 15461, -2595, -89, 115, + 276, -963, -601, 20954, 15654, -2562, -110, 119, 270, -930, -694, 20810, 15846, -2527, -132, 123, + 265, -898, -785, 20664, 16036, -2490, -154, 128, 260, -866, -874, 20516, 16226, -2452, -176, 132, + 254, -834, -961, 20366, 16415, -2411, -199, 137, 249, -803, -1046, 20213, 16602, -2369, -222, 141, + 243, -771, -1129, 20059, 16789, -2326, -246, 146, 238, -740, -1209, 19902, 16974, -2280, -270, 151, + 233, -710, -1288, 19744, 17158, -2232, -294, 156, 227, -680, -1365, 19583, 17341, -2183, -319, 160, + 222, -650, -1440, 19421, 17523, -2132, -345, 165, 217, -620, -1513, 19257, 17703, -2079, -370, 170, + 211, -591, -1583, 19091, 17882, -2023, -396, 175, 206, -562, -1652, 18923, 18059, -1966, -423, 180, + 201, -533, -1719, 18754, 18235, -1907, -450, 185, 196, -505, -1784, 18582, 18410, -1847, -477, 191, + 191, -477, -1847, 18410, 18582, -1784, -505, 196, 185, -450, -1907, 18235, 18754, -1719, -533, 201, + 180, -423, -1966, 18059, 18923, -1652, -562, 206, 175, -396, -2023, 17882, 19091, -1583, -591, 211, + 170, -370, -2079, 17703, 19257, -1513, -620, 217, 165, -345, -2132, 17523, 19421, -1440, -650, 222, + 160, -319, -2183, 17341, 19583, -1365, -680, 227, 156, -294, -2232, 17158, 19744, -1288, -710, 233, + 151, -270, -2280, 16974, 19902, -1209, -740, 238, 146, -246, -2326, 16789, 20059, -1129, -771, 243, + 141, -222, -2369, 16602, 20213, -1046, -803, 249, 137, -199, -2411, 16415, 20366, -961, -834, 254, + 132, -176, -2452, 16226, 20516, -874, -866, 260, 128, -154, -2490, 16036, 20664, -785, -898, 265, + 123, -132, -2527, 15846, 20810, -694, -930, 270, 119, -110, -2562, 15654, 20954, -601, -963, 276, + 115, -89, -2595, 15461, 21095, -506, -996, 281, 111, -69, -2626, 15268, 21234, -409, -1029, 286, + 106, -48, -2656, 15074, 21371, -310, -1062, 292, 102, -29, -2684, 14879, 21506, -208, -1096, 297, + 98, -9, -2710, 14684, 21637, -105, -1129, 302, 95, 9, -2735, 14488, 21767, 0, -1163, 307, + 91, 28, -2758, 14291, 21894, 107, -1197, 313, 87, 46, -2779, 14094, 22019, 217, -1232, 318, + 83, 63, -2799, 13896, 22141, 328, -1266, 323, 80, 80, -2817, 13698, 22260, 441, -1301, 328, + 76, 97, -2834, 13499, 22377, 557, -1335, 333, 73, 113, -2849, 13300, 22491, 674, -1370, 338, + 69, 128, -2863, 13101, 22602, 793, -1405, 342, 66, 143, -2875, 12901, 22711, 915, -1440, 347, + 63, 158, -2885, 12702, 22817, 1038, -1475, 352, 60, 173, -2895, 12502, 22920, 1163, -1510, 356, + 57, 186, -2902, 12302, 23020, 1291, -1545, 361, 54, 200, -2909, 12102, 23118, 1420, -1580, 365, + 51, 213, -2914, 11902, 23212, 1551, -1615, 369, 48, 225, -2917, 11702, 23304, 1684, -1649, 373, + 45, 238, -2920, 11502, 23393, 1819, -1684, 377, 43, 249, -2921, 11303, 23478, 1956, -1719, 381, + 40, 261, -2920, 11103, 23561, 2095, -1754, 385, 38, 271, -2919, 10904, 23641, 2235, -1789, 389, + 35, 282, -2916, 10705, 23718, 2378, -1823, 392, 33, 292, -2912, 10506, 23792, 2522, -1858, 395, + 31, 301, -2907, 10308, 23863, 2668, -1892, 398, 29, 311, -2900, 10110, 23930, 2816, -1926, 402, + 26, 319, -2893, 9912, 23995, 2966, -1960, 404, 24, 328, -2884, 9715, 24056, 3117, -1993, 407, + 22, 336, -2874, 9519, 24115, 3270, -2027, 410, 21, 344, -2863, 9323, 24170, 3425, -2060, 412, + 19, 351, -2851, 9127, 24222, 3582, -2093, 414, 17, 358, -2839, 8933, 24271, 3740, -2126, 416, + 15, 364, -2825, 8739, 24316, 3900, -2158, 418, 14, 370, -2810, 8545, 24359, 4062, -2190, 419, + 12, 376, -2794, 8353, 24398, 4225, -2221, 420, 11, 381, -2777, 8161, 24434, 4390, -2252, 421, + 9, 386, -2759, 7970, 24467, 4556, -2283, 422, 8, 391, -2741, 7780, 24496, 4724, -2313, 423, + 7, 395, -2721, 7591, 24523, 4893, -2343, 423, 6, 399, -2701, 7403, 24546, 5064, -2373, 423, + 5, 403, -2680, 7216, 24565, 5236, -2402, 423, 4, 406, -2658, 7030, 24582, 5410, -2430, 423, + 3, 409, -2635, 6846, 24595, 5585, -2458, 422, 2, 412, -2612, 6662, 24605, 5761, -2485, 421, + 1, 415, -2588, 6479, 24611, 5939, -2512, 420, 0, 417, -2563, 6298, 24615, 6118, -2538, 418, + }; + #endregion + + private static float[] _normalCurveLut0F; + private static float[] _normalCurveLut1F; + private static float[] _normalCurveLut2F; + + private static float[] _highCurveLut0F; + private static float[] _highCurveLut1F; + private static float[] _highCurveLut2F; + + static ResamplerHelper() + { + _normalCurveLut0F = _normalCurveLut0.Select(x => x / 32768f).ToArray(); + _normalCurveLut1F = _normalCurveLut1.Select(x => x / 32768f).ToArray(); + _normalCurveLut2F = _normalCurveLut2.Select(x => x / 32768f).ToArray(); + + _highCurveLut0F = _highCurveLut0.Select(x => x / 32768f).ToArray(); + _highCurveLut1F = _highCurveLut1.Select(x => x / 32768f).ToArray(); + _highCurveLut2F = _highCurveLut2.Select(x => x / 32768f).ToArray(); + } + + private const int FixedPointPrecision = 15; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Resample(Span outputBuffer, ReadOnlySpan inputBuffer, float ratio, ref float fraction, int sampleCount, SampleRateConversionQuality srcQuality, bool needPitch) + { + switch (srcQuality) + { + case SampleRateConversionQuality.Default: + ResampleDefaultQuality(outputBuffer, inputBuffer, ratio, ref fraction, sampleCount, needPitch); + break; + case SampleRateConversionQuality.Low: + ResampleLowQuality(outputBuffer, inputBuffer, ratio, ref fraction, sampleCount); + break; + case SampleRateConversionQuality.High: + ResampleHighQuality(outputBuffer, inputBuffer, ratio, ref fraction, sampleCount); + break; + default: + throw new NotImplementedException($"{srcQuality}"); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ReadOnlySpan GetDefaultParameter(float ratio) + { + if (ratio <= 1.0f) + { + return _normalCurveLut1F; + } + else if (ratio > 1.333313f) + { + return _normalCurveLut0F; + } + + return _normalCurveLut2F; + } + + private unsafe static void ResampleDefaultQuality(Span outputBuffer, ReadOnlySpan inputBuffer, float ratio, ref float fraction, int sampleCount, bool needPitch) + { + ReadOnlySpan parameters = GetDefaultParameter(ratio); + + int inputBufferIndex = 0, i = 0; + + // TODO: REV8 fast path (when needPitch == false the input index progression is constant + we need SIMD) + + if (Sse41.IsSupported) + { + if (ratio == 1f) + { + fixed (short* pInput = inputBuffer) + fixed (float* pOutput = outputBuffer, pParameters = parameters) + { + Vector128 parameter = Sse.LoadVector128(pParameters); + + for (; i < (sampleCount & ~3); i += 4) + { + Vector128 intInput0 = Sse41.ConvertToVector128Int32(pInput + (uint)i); + Vector128 intInput1 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 1); + Vector128 intInput2 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 2); + Vector128 intInput3 = Sse41.ConvertToVector128Int32(pInput + (uint)i + 3); + + Vector128 input0 = Sse2.ConvertToVector128Single(intInput0); + Vector128 input1 = Sse2.ConvertToVector128Single(intInput1); + Vector128 input2 = Sse2.ConvertToVector128Single(intInput2); + Vector128 input3 = Sse2.ConvertToVector128Single(intInput3); + + Vector128 mix0 = Sse.Multiply(input0, parameter); + Vector128 mix1 = Sse.Multiply(input1, parameter); + Vector128 mix2 = Sse.Multiply(input2, parameter); + Vector128 mix3 = Sse.Multiply(input3, parameter); + + Vector128 mix01 = Sse3.HorizontalAdd(mix0, mix1); + Vector128 mix23 = Sse3.HorizontalAdd(mix2, mix3); + + Vector128 mix0123 = Sse3.HorizontalAdd(mix01, mix23); + + Sse.Store(pOutput + (uint)i, Sse41.RoundToNearestInteger(mix0123)); + } + } + + inputBufferIndex = i; + } + else + { + fixed (short* pInput = inputBuffer) + fixed (float* pOutput = outputBuffer, pParameters = parameters) + { + for (; i < (sampleCount & ~3); i += 4) + { + uint baseIndex0 = (uint)(fraction * 128) * 4; + uint inputIndex0 = (uint)inputBufferIndex; + + fraction += ratio; + + uint baseIndex1 = ((uint)(fraction * 128) & 127) * 4; + uint inputIndex1 = (uint)inputBufferIndex + (uint)fraction; + + fraction += ratio; + + uint baseIndex2 = ((uint)(fraction * 128) & 127) * 4; + uint inputIndex2 = (uint)inputBufferIndex + (uint)fraction; + + fraction += ratio; + + uint baseIndex3 = ((uint)(fraction * 128) & 127) * 4; + uint inputIndex3 = (uint)inputBufferIndex + (uint)fraction; + + fraction += ratio; + inputBufferIndex += (int)fraction; + + // Only keep lower part (safe as fraction isn't supposed to be negative) + fraction -= (int)fraction; + + Vector128 parameter0 = Sse.LoadVector128(pParameters + baseIndex0); + Vector128 parameter1 = Sse.LoadVector128(pParameters + baseIndex1); + Vector128 parameter2 = Sse.LoadVector128(pParameters + baseIndex2); + Vector128 parameter3 = Sse.LoadVector128(pParameters + baseIndex3); + + Vector128 intInput0 = Sse41.ConvertToVector128Int32(pInput + inputIndex0); + Vector128 intInput1 = Sse41.ConvertToVector128Int32(pInput + inputIndex1); + Vector128 intInput2 = Sse41.ConvertToVector128Int32(pInput + inputIndex2); + Vector128 intInput3 = Sse41.ConvertToVector128Int32(pInput + inputIndex3); + + Vector128 input0 = Sse2.ConvertToVector128Single(intInput0); + Vector128 input1 = Sse2.ConvertToVector128Single(intInput1); + Vector128 input2 = Sse2.ConvertToVector128Single(intInput2); + Vector128 input3 = Sse2.ConvertToVector128Single(intInput3); + + Vector128 mix0 = Sse.Multiply(input0, parameter0); + Vector128 mix1 = Sse.Multiply(input1, parameter1); + Vector128 mix2 = Sse.Multiply(input2, parameter2); + Vector128 mix3 = Sse.Multiply(input3, parameter3); + + Vector128 mix01 = Sse3.HorizontalAdd(mix0, mix1); + Vector128 mix23 = Sse3.HorizontalAdd(mix2, mix3); + + Vector128 mix0123 = Sse3.HorizontalAdd(mix01, mix23); + + Sse.Store(pOutput + (uint)i, Sse41.RoundToNearestInteger(mix0123)); + } + } + } + } + + for (; i < sampleCount; i++) + { + int baseIndex = (int)(fraction * 128) * 4; + ReadOnlySpan parameter = parameters.Slice(baseIndex, 4); + ReadOnlySpan currentInput = inputBuffer.Slice(inputBufferIndex, 4); + + outputBuffer[i] = (float)Math.Round(currentInput[0] * parameter[0] + + currentInput[1] * parameter[1] + + currentInput[2] * parameter[2] + + currentInput[3] * parameter[3]); + + fraction += ratio; + inputBufferIndex += (int)fraction; + + // Only keep lower part (safe as fraction isn't supposed to be negative) + fraction -= (int)fraction; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ReadOnlySpan GetHighParameter(float ratio) + { + if (ratio <= 1.0f) + { + return _highCurveLut1F; + } + else if (ratio > 1.333313f) + { + return _highCurveLut0F; + } + + return _highCurveLut2F; + } + + private static unsafe void ResampleHighQuality(Span outputBuffer, ReadOnlySpan inputBuffer, float ratio, ref float fraction, int sampleCount) + { + ReadOnlySpan parameters = GetHighParameter(ratio); + + int inputBufferIndex = 0; + + if (Avx2.IsSupported) + { + // Fast path; assumes 256-bit vectors for simplicity because the filter is 8 taps + fixed (short* pInput = inputBuffer) + fixed (float* pParameters = parameters) + { + for (int i = 0; i < sampleCount; i++) + { + int baseIndex = (int)(fraction * 128) * 8; + + Vector256 intInput = Avx2.ConvertToVector256Int32(pInput + inputBufferIndex); + Vector256 floatInput = Avx.ConvertToVector256Single(intInput); + Vector256 parameter = Avx.LoadVector256(pParameters + baseIndex); + Vector256 dp = Avx.DotProduct(floatInput, parameter, control: 0xFF); + + // avx2 does an 8-element dot product piecewise so we have to sum up 2 intermediate results + outputBuffer[i] = (float)Math.Round(dp[0] + dp[4]); + + fraction += ratio; + inputBufferIndex += (int)MathF.Truncate(fraction); + + fraction -= (int)fraction; + } + } + } + else + { + for (int i = 0; i < sampleCount; i++) + { + int baseIndex = (int)(fraction * 128) * 8; + ReadOnlySpan parameter = parameters.Slice(baseIndex, 8); + ReadOnlySpan currentInput = inputBuffer.Slice(inputBufferIndex, 8); + + outputBuffer[i] = (float)Math.Round(currentInput[0] * parameter[0] + + currentInput[1] * parameter[1] + + currentInput[2] * parameter[2] + + currentInput[3] * parameter[3] + + currentInput[4] * parameter[4] + + currentInput[5] * parameter[5] + + currentInput[6] * parameter[6] + + currentInput[7] * parameter[7]); + + fraction += ratio; + inputBufferIndex += (int)MathF.Truncate(fraction); + + fraction -= (int)fraction; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ResampleLowQuality(Span outputBuffer, ReadOnlySpan inputBuffer, float ratio, ref float fraction, int sampleCount) + { + int inputBufferIndex = 0; + + for (int i = 0; i < sampleCount; i++) + { + int outputData = inputBuffer[inputBufferIndex]; + + if (fraction > 1.0f) + { + outputData = inputBuffer[inputBufferIndex + 1]; + } + + outputBuffer[i] = outputData; + + fraction += ratio; + inputBufferIndex += (int)MathF.Truncate(fraction); + + fraction -= (int)fraction; + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/AdpcmLoopContext.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/AdpcmLoopContext.cs new file mode 100644 index 00000000..821a135e --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/AdpcmLoopContext.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 6)] + public struct AdpcmLoopContext + { + public short PredScale; + public short History0; + public short History1; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/AuxiliaryBufferHeader.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/AuxiliaryBufferHeader.cs new file mode 100644 index 00000000..4e8d11e4 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/AuxiliaryBufferHeader.cs @@ -0,0 +1,74 @@ +using Ryujinx.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x80)] + public struct AuxiliaryBufferHeader + { + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x40)] + public struct AuxiliaryBufferInfo + { + private const uint ReadOffsetPosition = 0x0; + private const uint WriteOffsetPosition = 0x4; + private const uint LostSampleCountPosition = 0x8; + private const uint TotalSampleCountPosition = 0xC; + + public uint ReadOffset; + public uint WriteOffset; + public uint LostSampleCount; + public uint TotalSampleCount; + private unsafe fixed uint _unknown[12]; + + public static uint GetReadOffset(IVirtualMemoryManager manager, ulong bufferAddress) + { + return manager.Read(bufferAddress + ReadOffsetPosition); + } + + public static uint GetWriteOffset(IVirtualMemoryManager manager, ulong bufferAddress) + { + return manager.Read(bufferAddress + WriteOffsetPosition); + } + + public static uint GetLostSampleCount(IVirtualMemoryManager manager, ulong bufferAddress) + { + return manager.Read(bufferAddress + LostSampleCountPosition); + } + + public static uint GetTotalSampleCount(IVirtualMemoryManager manager, ulong bufferAddress) + { + return manager.Read(bufferAddress + TotalSampleCountPosition); + } + + public static void SetReadOffset(IVirtualMemoryManager manager, ulong bufferAddress, uint value) + { + manager.Write(bufferAddress + ReadOffsetPosition, value); + } + + public static void SetWriteOffset(IVirtualMemoryManager manager, ulong bufferAddress, uint value) + { + manager.Write(bufferAddress + WriteOffsetPosition, value); + } + + public static void SetLostSampleCount(IVirtualMemoryManager manager, ulong bufferAddress, uint value) + { + manager.Write(bufferAddress + LostSampleCountPosition, value); + } + + public static void SetTotalSampleCount(IVirtualMemoryManager manager, ulong bufferAddress, uint value) + { + manager.Write(bufferAddress + TotalSampleCountPosition, value); + } + + public static void Reset(IVirtualMemoryManager manager, ulong bufferAddress) + { + // NOTE: Lost sample count is never reset, since REV10. + manager.Write(bufferAddress + ReadOffsetPosition, 0UL); + manager.Write(bufferAddress + TotalSampleCountPosition, 0); + } + } + + public AuxiliaryBufferInfo CpuBufferInfo; + public AuxiliaryBufferInfo DspBufferInfo; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs new file mode 100644 index 00000000..4220e6d5 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] + public struct BiquadFilterState + { + public float State0; + public float State1; + public float State2; + public float State3; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/CompressorState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/CompressorState.cs new file mode 100644 index 00000000..76aff807 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/CompressorState.cs @@ -0,0 +1,51 @@ +using Ryujinx.Audio.Renderer.Dsp.Effect; +using Ryujinx.Audio.Renderer.Parameter.Effect; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + public class CompressorState + { + public ExponentialMovingAverage InputMovingAverage; + public float Unknown4; + public ExponentialMovingAverage CompressionGainAverage; + public float CompressorGainReduction; + public float Unknown10; + public float Unknown14; + public float PreviousCompressionEmaAlpha; + public float MakeupGain; + public float OutputGain; + + public CompressorState(ref CompressorParameter parameter) + { + InputMovingAverage = new ExponentialMovingAverage(0.0f); + Unknown4 = 1.0f; + CompressionGainAverage = new ExponentialMovingAverage(1.0f); + + UpdateParameter(ref parameter); + } + + public void UpdateParameter(ref CompressorParameter parameter) + { + float threshold = parameter.Threshold; + float ratio = 1.0f / parameter.Ratio; + float attackCoefficient = parameter.AttackCoefficient; + float makeupGain; + + if (parameter.MakeupGainEnabled) + { + makeupGain = (threshold * 0.5f * (ratio - 1.0f)) - 3.0f; + } + else + { + makeupGain = 0.0f; + } + + PreviousCompressionEmaAlpha = attackCoefficient; + MakeupGain = makeupGain; + CompressorGainReduction = (1.0f - ratio) / Constants.ChannelCountMax; + Unknown10 = threshold - 1.5f; + Unknown14 = threshold + 1.5f; + OutputGain = FloatingPointHelper.DecibelToLinearExtended(parameter.OutputGain + makeupGain); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs new file mode 100644 index 00000000..2a1e7f83 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs @@ -0,0 +1,67 @@ +using Ryujinx.Audio.Renderer.Dsp.Effect; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + public class DelayState + { + public DelayLine[] DelayLines { get; } + public float[] LowPassZ { get; set; } + public float FeedbackGain { get; private set; } + public float DelayFeedbackBaseGain { get; private set; } + public float DelayFeedbackCrossGain { get; private set; } + public float LowPassFeedbackGain { get; private set; } + public float LowPassBaseGain { get; private set; } + + private const int FixedPointPrecision = 14; + + public DelayState(ref DelayParameter parameter, ulong workBuffer) + { + DelayLines = new DelayLine[parameter.ChannelCount]; + LowPassZ = new float[parameter.ChannelCount]; + + uint sampleRate = (uint)FixedPointHelper.ToInt(parameter.SampleRate, FixedPointPrecision) / 1000; + + for (int i = 0; i < DelayLines.Length; i++) + { + DelayLines[i] = new DelayLine(sampleRate, parameter.DelayTimeMax); + DelayLines[i].SetDelay(parameter.DelayTime); + } + + UpdateParameter(ref parameter); + } + + public void UpdateParameter(ref DelayParameter parameter) + { + FeedbackGain = FixedPointHelper.ToFloat(parameter.FeedbackGain, FixedPointPrecision) * 0.98f; + + float channelSpread = FixedPointHelper.ToFloat(parameter.ChannelSpread, FixedPointPrecision); + + DelayFeedbackBaseGain = (1.0f - channelSpread) * FeedbackGain; + + if (parameter.ChannelCount == 4 || parameter.ChannelCount == 6) + { + DelayFeedbackCrossGain = channelSpread * 0.5f * FeedbackGain; + } + else + { + DelayFeedbackCrossGain = channelSpread * FeedbackGain; + } + + LowPassFeedbackGain = 0.95f * FixedPointHelper.ToFloat(parameter.LowPassAmount, FixedPointPrecision); + LowPassBaseGain = 1.0f - LowPassFeedbackGain; + } + + public void UpdateLowPassFilter(ref float tempRawRef, uint channelCount) + { + for (int i = 0; i < channelCount; i++) + { + float lowPassResult = LowPassFeedbackGain * LowPassZ[i] + Unsafe.Add(ref tempRawRef, i) * LowPassBaseGain; + + LowPassZ[i] = lowPassResult; + DelayLines[i].Update(lowPassResult); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs new file mode 100644 index 00000000..0560757c --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs @@ -0,0 +1,31 @@ +using Ryujinx.Audio.Renderer.Dsp.Effect; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + public class LimiterState + { + public ExponentialMovingAverage[] DetectorAverage; + public ExponentialMovingAverage[] CompressionGainAverage; + public float[] DelayedSampleBuffer; + public int[] DelayedSampleBufferPosition; + + public LimiterState(ref LimiterParameter parameter, ulong workBuffer) + { + DetectorAverage = new ExponentialMovingAverage[parameter.ChannelCount]; + CompressionGainAverage = new ExponentialMovingAverage[parameter.ChannelCount]; + DelayedSampleBuffer = new float[parameter.ChannelCount * parameter.DelayBufferSampleCountMax]; + DelayedSampleBufferPosition = new int[parameter.ChannelCount]; + + DetectorAverage.AsSpan().Fill(new ExponentialMovingAverage(0.0f)); + CompressionGainAverage.AsSpan().Fill(new ExponentialMovingAverage(1.0f)); + DelayedSampleBufferPosition.AsSpan().Fill(0); + DelayedSampleBuffer.AsSpan().Fill(0.0f); + + UpdateParameter(ref parameter); + } + + public void UpdateParameter(ref LimiterParameter parameter) { } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/Reverb3dState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/Reverb3dState.cs new file mode 100644 index 00000000..c0646603 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/Reverb3dState.cs @@ -0,0 +1,119 @@ +using Ryujinx.Audio.Renderer.Dsp.Effect; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + public class Reverb3dState + { + private readonly float[] FdnDelayMinTimes = new float[4] { 5.0f, 6.0f, 13.0f, 14.0f }; + private readonly float[] FdnDelayMaxTimes = new float[4] { 45.704f, 82.782f, 149.94f, 271.58f }; + private readonly float[] DecayDelayMaxTimes1 = new float[4] { 17.0f, 13.0f, 9.0f, 7.0f }; + private readonly float[] DecayDelayMaxTimes2 = new float[4] { 19.0f, 11.0f, 10.0f, 6.0f }; + private readonly float[] EarlyDelayTimes = new float[20] { 0.017136f, 0.059154f, 0.16173f, 0.39019f, 0.42526f, 0.45541f, 0.68974f, 0.74591f, 0.83384f, 0.8595f, 0.0f, 0.075024f, 0.16879f, 0.2999f, 0.33744f, 0.3719f, 0.59901f, 0.71674f, 0.81786f, 0.85166f }; + public readonly float[] EarlyGain = new float[20] { 0.67096f, 0.61027f, 1.0f, 0.35680f, 0.68361f, 0.65978f, 0.51939f, 0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.38270f, 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f }; + + public IDelayLine[] FdnDelayLines { get; } + public DecayDelay[] DecayDelays1 { get; } + public DecayDelay[] DecayDelays2 { get; } + public IDelayLine PreDelayLine { get; } + public IDelayLine FrontCenterDelayLine { get; } + public float DryGain { get; private set; } + public uint[] EarlyDelayTime { get; private set; } + public float PreviousPreDelayValue { get; set; } + public float PreviousPreDelayGain { get; private set; } + public float TargetPreDelayGain { get; private set; } + public float EarlyReflectionsGain { get; private set; } + public float LateReverbGain { get; private set; } + public uint ReflectionDelayTime { get; private set; } + public float EchoLateReverbDecay { get; private set; } + public float[] DecayDirectFdnGain { get; private set; } + public float[] DecayCurrentFdnGain { get; private set; } + public float[] DecayCurrentOutputGain { get; private set; } + public float[] PreviousFeedbackOutputDecayed { get; private set; } + + public Reverb3dState(ref Reverb3dParameter parameter, ulong workBuffer) + { + FdnDelayLines = new IDelayLine[4]; + DecayDelays1 = new DecayDelay[4]; + DecayDelays2 = new DecayDelay[4]; + DecayDirectFdnGain = new float[4]; + DecayCurrentFdnGain = new float[4]; + DecayCurrentOutputGain = new float[4]; + PreviousFeedbackOutputDecayed = new float[4]; + + uint sampleRate = parameter.SampleRate / 1000; + + for (int i = 0; i < 4; i++) + { + FdnDelayLines[i] = new DelayLine3d(sampleRate, FdnDelayMaxTimes[i]); + DecayDelays1[i] = new DecayDelay(new DelayLine3d(sampleRate, DecayDelayMaxTimes1[i])); + DecayDelays2[i] = new DecayDelay(new DelayLine3d(sampleRate, DecayDelayMaxTimes2[i])); + } + + PreDelayLine = new DelayLine3d(sampleRate, 400); + FrontCenterDelayLine = new DelayLine3d(sampleRate, 5); + + UpdateParameter(ref parameter); + } + + public void UpdateParameter(ref Reverb3dParameter parameter) + { + uint sampleRate = parameter.SampleRate / 1000; + + EarlyDelayTime = new uint[20]; + DryGain = parameter.DryGain; + PreviousFeedbackOutputDecayed.AsSpan().Fill(0); + PreviousPreDelayValue = 0; + + EarlyReflectionsGain = FloatingPointHelper.Pow10(Math.Min(parameter.RoomGain + parameter.ReflectionsGain, 5000.0f) / 2000.0f); + LateReverbGain = FloatingPointHelper.Pow10(Math.Min(parameter.RoomGain + parameter.ReverbGain, 5000.0f) / 2000.0f); + + float highFrequencyRoomGain = FloatingPointHelper.Pow10(parameter.RoomHf / 2000.0f); + + if (highFrequencyRoomGain < 1.0f) + { + float tempA = 1.0f - highFrequencyRoomGain; + float tempB = 2.0f - ((2.0f * highFrequencyRoomGain) * FloatingPointHelper.Cos(256.0f * parameter.HfReference / parameter.SampleRate)); + float tempC = MathF.Sqrt(MathF.Pow(tempB, 2) - (4.0f * (1.0f - highFrequencyRoomGain) * (1.0f - highFrequencyRoomGain))); + + PreviousPreDelayGain = (tempB - tempC) / (2.0f * tempA); + TargetPreDelayGain = 1.0f - PreviousPreDelayGain; + } + else + { + PreviousPreDelayGain = 0.0f; + TargetPreDelayGain = 1.0f; + } + + ReflectionDelayTime = IDelayLine.GetSampleCount(sampleRate, 1000.0f * (parameter.ReflectionDelay + parameter.ReverbDelayTime)); + EchoLateReverbDecay = 0.6f * parameter.Diffusion * 0.01f; + + for (int i = 0; i < FdnDelayLines.Length; i++) + { + FdnDelayLines[i].SetDelay(FdnDelayMinTimes[i] + (parameter.Density / 100 * (FdnDelayMaxTimes[i] - FdnDelayMinTimes[i]))); + + uint tempSampleCount = FdnDelayLines[i].CurrentSampleCount + DecayDelays1[i].CurrentSampleCount + DecayDelays2[i].CurrentSampleCount; + + float tempA = (-60.0f * tempSampleCount) / (parameter.DecayTime * parameter.SampleRate); + float tempB = tempA / parameter.HfDecayRatio; + float tempC = FloatingPointHelper.Cos(128.0f * 0.5f * parameter.HfReference / parameter.SampleRate) / FloatingPointHelper.Sin(128.0f * 0.5f * parameter.HfReference / parameter.SampleRate); + float tempD = FloatingPointHelper.Pow10((tempB - tempA) / 40.0f); + float tempE = FloatingPointHelper.Pow10((tempB + tempA) / 40.0f) * 0.7071f; + + DecayDirectFdnGain[i] = tempE * ((tempD * tempC) + 1.0f) / (tempC + tempD); + DecayCurrentFdnGain[i] = tempE * (1.0f - (tempD * tempC)) / (tempC + tempD); + DecayCurrentOutputGain[i] = (tempC - tempD) / (tempC + tempD); + + DecayDelays1[i].SetDecayRate(EchoLateReverbDecay); + DecayDelays2[i].SetDecayRate(EchoLateReverbDecay * -0.9f); + } + + for (int i = 0; i < EarlyDelayTime.Length; i++) + { + uint sampleCount = Math.Min(IDelayLine.GetSampleCount(sampleRate, (parameter.ReflectionDelay * 1000.0f) + (EarlyDelayTimes[i] * 1000.0f * ((parameter.ReverbDelayTime * 0.9998f) + 0.02f))), PreDelayLine.SampleCountMax); + EarlyDelayTime[i] = sampleCount; + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/ReverbState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/ReverbState.cs new file mode 100644 index 00000000..1ffabe05 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/ReverbState.cs @@ -0,0 +1,204 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.Effect; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System; + +namespace Ryujinx.Audio.Renderer.Dsp.State +{ + public class ReverbState + { + private static readonly float[] FdnDelayTimes = new float[20] + { + // Room + 53.953247f, 79.192566f, 116.238770f, 130.615295f, + // Hall + 53.953247f, 79.192566f, 116.238770f, 170.615295f, + // Plate + 5f, 10f, 5f, 10f, + // Cathedral + 47.03f, 71f, 103f, 170f, + // Max delay (Hall is the one with the highest values so identical to Hall) + 53.953247f, 79.192566f, 116.238770f, 170.615295f, + }; + + private static readonly float[] DecayDelayTimes = new float[20] + { + // Room + 7f, 9f, 13f, 17f, + // Hall + 7f, 9f, 13f, 17f, + // Plate (no decay) + 1f, 1f, 1f, 1f, + // Cathedral + 7f, 7f, 13f, 9f, + // Max delay (Hall is the one with the highest values so identical to Hall) + 7f, 9f, 13f, 17f, + }; + + private static readonly float[] EarlyDelayTimes = new float[50] + { + // Room + 0.0f, 3.5f, 2.8f, 3.9f, 2.7f, 13.4f, 7.9f, 8.4f, 9.9f, 12.0f, + // Chamber + 0.0f, 11.8f, 5.5f, 11.2f, 10.4f, 38.1f, 22.2f, 29.6f, 21.2f, 24.8f, + // Hall + 0.0f, 41.5f, 20.5f, 41.3f, 0.0f, 29.5f, 33.8f, 45.2f, 46.8f, 0.0f, + // Cathedral + 33.1f, 43.3f, 22.8f, 37.9f, 14.9f, 35.3f, 17.9f, 34.2f, 0.0f, 43.3f, + // Disabled + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f + }; + + private static readonly float[] EarlyGainBase = new float[50] + { + // Room + 0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.68f, 0.68f, + // Chamber + 0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.68f, 0.68f, 0.68f, 0.68f, + // Hall + 0.50f, 0.70f, 0.70f, 0.68f, 0.50f, 0.68f, 0.68f, 0.70f, 0.68f, 0.00f, + // Cathedral + 0.93f, 0.92f, 0.87f, 0.86f, 0.94f, 0.81f, 0.80f, 0.77f, 0.76f, 0.65f, + // Disabled + 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f, 0.00f + }; + + private static readonly float[] PreDelayTimes = new float[5] + { + // Room + 12.5f, + // Chamber + 40.0f, + // Hall + 50.0f, + // Cathedral + 50.0f, + // Disabled + 0.0f + }; + + public DelayLine[] FdnDelayLines { get; } + public DecayDelay[] DecayDelays { get; } + public DelayLine PreDelayLine { get; } + public DelayLine FrontCenterDelayLine { get; } + public uint[] EarlyDelayTime { get; } + public float[] EarlyGain { get; } + public uint PreDelayLineDelayTime { get; private set; } + + public float[] HighFrequencyDecayDirectGain { get; } + public float[] HighFrequencyDecayPreviousGain { get; } + public float[] PreviousFeedbackOutput { get; } + + public const int EarlyModeCount = 10; + + private const int FixedPointPrecision = 14; + + private ReadOnlySpan GetFdnDelayTimesByLateMode(ReverbLateMode lateMode) + { + return FdnDelayTimes.AsSpan((int)lateMode * 4, 4); + } + + private ReadOnlySpan GetDecayDelayTimesByLateMode(ReverbLateMode lateMode) + { + return DecayDelayTimes.AsSpan((int)lateMode * 4, 4); + } + + public ReverbState(ref ReverbParameter parameter, ulong workBuffer, bool isLongSizePreDelaySupported) + { + FdnDelayLines = new DelayLine[4]; + DecayDelays = new DecayDelay[4]; + EarlyDelayTime = new uint[EarlyModeCount]; + EarlyGain = new float[EarlyModeCount]; + HighFrequencyDecayDirectGain = new float[4]; + HighFrequencyDecayPreviousGain = new float[4]; + PreviousFeedbackOutput = new float[4]; + + ReadOnlySpan fdnDelayTimes = GetFdnDelayTimesByLateMode(ReverbLateMode.Limit); + ReadOnlySpan decayDelayTimes = GetDecayDelayTimesByLateMode(ReverbLateMode.Limit); + + uint sampleRate = (uint)FixedPointHelper.ToFloat((uint)parameter.SampleRate, FixedPointPrecision); + + for (int i = 0; i < 4; i++) + { + FdnDelayLines[i] = new DelayLine(sampleRate, fdnDelayTimes[i]); + DecayDelays[i] = new DecayDelay(new DelayLine(sampleRate, decayDelayTimes[i])); + } + + float preDelayTimeMax = 150.0f; + + if (isLongSizePreDelaySupported) + { + preDelayTimeMax = 350.0f; + } + + PreDelayLine = new DelayLine(sampleRate, preDelayTimeMax); + FrontCenterDelayLine = new DelayLine(sampleRate, 5.0f); + + UpdateParameter(ref parameter); + } + + public void UpdateParameter(ref ReverbParameter parameter) + { + uint sampleRate = (uint)FixedPointHelper.ToFloat((uint)parameter.SampleRate, FixedPointPrecision); + + float preDelayTimeInMilliseconds = FixedPointHelper.ToFloat(parameter.PreDelayTime, FixedPointPrecision); + float earlyGain = FixedPointHelper.ToFloat(parameter.EarlyGain, FixedPointPrecision); + float coloration = FixedPointHelper.ToFloat(parameter.Coloration, FixedPointPrecision); + float decayTime = FixedPointHelper.ToFloat(parameter.DecayTime, FixedPointPrecision); + + for (int i = 0; i < 10; i++) + { + EarlyDelayTime[i] = Math.Min(IDelayLine.GetSampleCount(sampleRate, EarlyDelayTimes[i] + preDelayTimeInMilliseconds), PreDelayLine.SampleCountMax) + 1; + EarlyGain[i] = EarlyGainBase[i] * earlyGain; + } + + if (parameter.ChannelCount == 2) + { + EarlyGain[4] = EarlyGain[4] * 0.5f; + EarlyGain[5] = EarlyGain[5] * 0.5f; + } + + PreDelayLineDelayTime = Math.Min(IDelayLine.GetSampleCount(sampleRate, PreDelayTimes[(int)parameter.EarlyMode] + preDelayTimeInMilliseconds), PreDelayLine.SampleCountMax); + + ReadOnlySpan fdnDelayTimes = GetFdnDelayTimesByLateMode(parameter.LateMode); + ReadOnlySpan decayDelayTimes = GetDecayDelayTimesByLateMode(parameter.LateMode); + + float highFrequencyDecayRatio = FixedPointHelper.ToFloat(parameter.HighFrequencyDecayRatio, FixedPointPrecision); + float highFrequencyUnknownValue = FloatingPointHelper.Cos(1280.0f / sampleRate); + + for (int i = 0; i < 4; i++) + { + FdnDelayLines[i].SetDelay(fdnDelayTimes[i]); + DecayDelays[i].SetDelay(decayDelayTimes[i]); + + float tempA = -3 * (DecayDelays[i].CurrentSampleCount + FdnDelayLines[i].CurrentSampleCount); + float tempB = tempA / (decayTime * sampleRate); + float tempC; + float tempD; + + if (highFrequencyDecayRatio < 0.995f) + { + float tempE = FloatingPointHelper.Pow10((((1.0f / highFrequencyDecayRatio) - 1.0f) * 2) / 100 * (tempB / 10)); + float tempF = 1.0f - tempE; + float tempG = 2.0f - (tempE * 2 * highFrequencyUnknownValue); + float tempH = MathF.Sqrt((tempG * tempG) - (tempF * tempF * 4)); + + tempC = (tempG - tempH) / (tempF * 2); + tempD = 1.0f - tempC; + } + else + { + // no high frequency decay ratio + tempC = 0.0f; + tempD = 1.0f; + } + + HighFrequencyDecayDirectGain[i] = FloatingPointHelper.Pow10(tempB / 1000) * tempD * 0.7071f; + HighFrequencyDecayPreviousGain[i] = tempC; + PreviousFeedbackOutput[i] = 0.0f; + + DecayDelays[i].SetDecayRate(0.6f * (1.0f - coloration)); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs new file mode 100644 index 00000000..6cdab5a7 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Dsp/UpsamplerHelper.cs @@ -0,0 +1,192 @@ +using Ryujinx.Audio.Renderer.Server.Upsampler; +using Ryujinx.Common.Memory; +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + public class UpsamplerHelper + { + private const int HistoryLength = UpsamplerBufferState.HistoryLength; + private const int FilterBankLength = 20; + // Bank0 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + private const int Bank0CenterIndex = 9; + private static readonly Array20 Bank1 = PrecomputeFilterBank(1.0f / 6.0f); + private static readonly Array20 Bank2 = PrecomputeFilterBank(2.0f / 6.0f); + private static readonly Array20 Bank3 = PrecomputeFilterBank(3.0f / 6.0f); + private static readonly Array20 Bank4 = PrecomputeFilterBank(4.0f / 6.0f); + private static readonly Array20 Bank5 = PrecomputeFilterBank(5.0f / 6.0f); + + private static Array20 PrecomputeFilterBank(float offset) + { + float Sinc(float x) + { + if (x == 0) + { + return 1.0f; + } + return (MathF.Sin(MathF.PI * x) / (MathF.PI * x)); + } + + float BlackmanWindow(float x) + { + const float a = 0.18f; + const float a0 = 0.5f - 0.5f * a; + const float a1 = -0.5f; + const float a2 = 0.5f * a; + return a0 + a1 * MathF.Cos(2 * MathF.PI * x) + a2 * MathF.Cos(4 * MathF.PI * x); + } + + Array20 result = new Array20(); + + for (int i = 0; i < FilterBankLength; i++) + { + float x = (Bank0CenterIndex - i) + offset; + result[i] = Sinc(x) * BlackmanWindow(x / FilterBankLength + 0.5f); + } + + return result; + } + + // Polyphase upsampling algorithm + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Upsample(Span outputBuffer, ReadOnlySpan inputBuffer, int outputSampleCount, int inputSampleCount, ref UpsamplerBufferState state) + { + if (!state.Initialized) + { + state.Scale = inputSampleCount switch + { + 40 => 6.0f, + 80 => 3.0f, + 160 => 1.5f, + _ => throw new ArgumentOutOfRangeException() + }; + state.Initialized = true; + } + + if (outputSampleCount == 0) + { + return; + } + + float DoFilterBank(ref UpsamplerBufferState state, in Array20 bank) + { + float result = 0.0f; + + Debug.Assert(state.History.Length == HistoryLength); + Debug.Assert(bank.Length == FilterBankLength); + + int curIdx = 0; + if (Vector.IsHardwareAccelerated) + { + // Do SIMD-accelerated block operations where possible. + // Only about a 2x speedup since filter bank length is short + int stopIdx = FilterBankLength - (FilterBankLength % Vector.Count); + while (curIdx < stopIdx) + { + result += Vector.Dot( + new Vector(bank.AsSpan().Slice(curIdx, Vector.Count)), + new Vector(state.History.AsSpan().Slice(curIdx, Vector.Count))); + curIdx += Vector.Count; + } + } + + while (curIdx < FilterBankLength) + { + result += bank[curIdx] * state.History[curIdx]; + curIdx++; + } + + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void NextInput(ref UpsamplerBufferState state, float input) + { + state.History.AsSpan().Slice(1).CopyTo(state.History.AsSpan()); + state.History[HistoryLength - 1] = input; + } + + int inputBufferIndex = 0; + + switch (state.Scale) + { + case 6.0f: + for (int i = 0; i < outputSampleCount; i++) + { + switch (state.Phase) + { + case 0: + NextInput(ref state, inputBuffer[inputBufferIndex++]); + outputBuffer[i] = state.History[Bank0CenterIndex]; + break; + case 1: + outputBuffer[i] = DoFilterBank(ref state, Bank1); + break; + case 2: + outputBuffer[i] = DoFilterBank(ref state, Bank2); + break; + case 3: + outputBuffer[i] = DoFilterBank(ref state, Bank3); + break; + case 4: + outputBuffer[i] = DoFilterBank(ref state, Bank4); + break; + case 5: + outputBuffer[i] = DoFilterBank(ref state, Bank5); + break; + } + + state.Phase = (state.Phase + 1) % 6; + } + break; + case 3.0f: + for (int i = 0; i < outputSampleCount; i++) + { + switch (state.Phase) + { + case 0: + NextInput(ref state, inputBuffer[inputBufferIndex++]); + outputBuffer[i] = state.History[Bank0CenterIndex]; + break; + case 1: + outputBuffer[i] = DoFilterBank(ref state, Bank2); + break; + case 2: + outputBuffer[i] = DoFilterBank(ref state, Bank4); + break; + } + + state.Phase = (state.Phase + 1) % 3; + } + break; + case 1.5f: + // Upsample by 3 then decimate by 2. + for (int i = 0; i < outputSampleCount; i++) + { + switch (state.Phase) + { + case 0: + NextInput(ref state, inputBuffer[inputBufferIndex++]); + outputBuffer[i] = state.History[Bank0CenterIndex]; + break; + case 1: + outputBuffer[i] = DoFilterBank(ref state, Bank4); + break; + case 2: + NextInput(ref state, inputBuffer[inputBufferIndex++]); + outputBuffer[i] = DoFilterBank(ref state, Bank2); + break; + } + + state.Phase = (state.Phase + 1) % 3; + } + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/AudioRendererConfiguration.cs b/src/Ryujinx.Audio/Renderer/Parameter/AudioRendererConfiguration.cs new file mode 100644 index 00000000..359cd4c0 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/AudioRendererConfiguration.cs @@ -0,0 +1,99 @@ +using Ryujinx.Audio.Renderer.Server.Types; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Audio Renderer user configuration. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AudioRendererConfiguration + { + /// + /// The target sample rate of the user. + /// + /// Only 32000Hz and 48000Hz are considered valid, other sample rates will cause undefined behaviour. + public uint SampleRate; + + /// + /// The target sample count per updates. + /// + public uint SampleCount; + + /// + /// The maximum mix buffer count. + /// + public uint MixBufferCount; + + /// + /// The maximum amount of sub mixes that could be used by the user. + /// + public uint SubMixBufferCount; + + /// + /// The maximum amount of voices that could be used by the user. + /// + public uint VoiceCount; + + /// + /// The maximum amount of sinks that could be used by the user. + /// + public uint SinkCount; + + /// + /// The maximum amount of effects that could be used by the user. + /// + public uint EffectCount; + + /// + /// The maximum amount of performance metric frames that could be used by the user. + /// + public uint PerformanceMetricFramesCount; + + /// + /// Set to true if the user allows the to drop voices. + /// + /// + [MarshalAs(UnmanagedType.I1)] + public bool VoiceDropEnabled; + + /// + /// Reserved/unused + /// + private byte _reserved; + + /// + /// The target rendering device. + /// + /// Must be + public AudioRendererRenderingDevice RenderingDevice; + + /// + /// The target execution mode. + /// + /// Must be + public AudioRendererExecutionMode ExecutionMode; + + /// + /// The maximum amount of splitters that could be used by the user. + /// + public uint SplitterCount; + + /// + /// The maximum amount of splitters destinations that could be used by the user. + /// + public uint SplitterDestinationCount; + + /// + /// The size of the external context. + /// + /// This is a leftover of the old "codec" interface system that was present between 1.0.0 and 3.0.0. This was entirely removed from the server side with REV8. + public uint ExternalContextSize; + + /// + /// The user audio revision + /// + /// + public int Revision; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs new file mode 100644 index 00000000..aba7dcd6 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs @@ -0,0 +1,30 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information for behaviour. + /// + /// This is used to report errors to the user during processing. + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BehaviourErrorInfoOutStatus + { + /// + /// The reported errors. + /// + public Array10 ErrorInfos; + + /// + /// The amount of error that got reported. + /// + public uint ErrorInfosCount; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[3]; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/BiquadFilterParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/BiquadFilterParameter.cs new file mode 100644 index 00000000..ef86015f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/BiquadFilterParameter.cs @@ -0,0 +1,34 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Biquad filter parameters. + /// + [StructLayout(LayoutKind.Sequential, Size = 0xC, Pack = 1)] + public struct BiquadFilterParameter + { + /// + /// Set to true if the biquad filter is active. + /// + [MarshalAs(UnmanagedType.I1)] + public bool Enable; + + /// + /// Reserved/padding. + /// + private byte _reserved; + + /// + /// Biquad filter numerator (b0, b1, b2). + /// + public Array3 Numerator; + + /// + /// Biquad filter denominator (a1, a2). + /// + /// a0 = 1 + public Array2 Denominator; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs new file mode 100644 index 00000000..36f28677 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs @@ -0,0 +1,84 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for and . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AuxiliaryBufferParameter + { + /// + /// The input channel indices that will be used by the to write data to . + /// + public Array24 Input; + + /// + /// The output channel indices that will be used by the to read data from . + /// + public Array24 Output; + + /// + /// The total channel count used. + /// + public uint ChannelCount; + + /// + /// The target sample rate. + /// + public uint SampleRate; + + /// + /// The buffer storage total size. + /// + public uint BufferStorageSize; + + /// + /// The maximum number of channels supported. + /// + /// This is unused. + public uint ChannelCountMax; + + /// + /// The address of the start of the region containing two followed by the data that will be written by the . + /// + public ulong SendBufferInfoAddress; + + /// + /// The address of the start of the region containling data that will be written by the . + /// + /// This is unused. + public ulong SendBufferStorageAddress; + + /// + /// The address of the start of the region containing two followed by the data that will be read by the . + /// + /// Unused with . + public ulong ReturnBufferInfoAddress; + + /// + /// The address of the start of the region containling data that will be read by the . + /// + /// This is unused. + public ulong ReturnBufferStorageAddress; + + /// + /// Size of a sample of the mix buffer. + /// + /// This is unused. + public uint MixBufferSampleSize; + + /// + /// The total count of sample that can be stored. + /// + /// This is unused. + public uint TotalSampleCount; + + /// + /// The count of sample of the mix buffer. + /// + /// This is unused. + public uint MixBufferSampleCount; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs new file mode 100644 index 00000000..73e0e9bb --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs @@ -0,0 +1,44 @@ +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BiquadFilterEffectParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// Biquad filter numerator (b0, b1, b2). + /// + public Array3 Numerator; + + /// + /// Biquad filter denominator (a1, a2). + /// + /// a0 = 1 + public Array2 Denominator; + + /// + /// The total channel count used. + /// + public byte ChannelCount; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState Status; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs new file mode 100644 index 00000000..b03559eb --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs @@ -0,0 +1,32 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BufferMixParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array24 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array24 Output; + + /// + /// The output volumes of the mixes. + /// + public Array24 Volumes; + + /// + /// The total count of mixes used. + /// + public uint MixesCount; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs new file mode 100644 index 00000000..0be37608 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/CompressorParameter.cs @@ -0,0 +1,115 @@ +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CompressorParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// The maximum number of channels supported. + /// + public ushort ChannelCountMax; + + /// + /// The total channel count used. + /// + public ushort ChannelCount; + + /// + /// The target sample rate. + /// + /// This is in kHz. + public int SampleRate; + + /// + /// The threshold. + /// + public float Threshold; + + /// + /// The compressor ratio. + /// + public float Ratio; + + /// + /// The attack time. + /// This is in microseconds. + /// + public int AttackTime; + + /// + /// The release time. + /// This is in microseconds. + /// + public int ReleaseTime; + + /// + /// The input gain. + /// + public float InputGain; + + /// + /// The attack coefficient. + /// + public float AttackCoefficient; + + /// + /// The release coefficient. + /// + public float ReleaseCoefficient; + + /// + /// The output gain. + /// + public float OutputGain; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState Status; + + /// + /// Indicate if the makeup gain should be used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool MakeupGainEnabled; + + /// + /// Reserved/padding. + /// + private Array2 _reserved; + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public bool IsChannelCountValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCount); + } + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public bool IsChannelCountMaxValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax); + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs new file mode 100644 index 00000000..72332c17 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs @@ -0,0 +1,101 @@ +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DelayParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// The maximum number of channels supported. + /// + public ushort ChannelCountMax; + + /// + /// The total channel count used. + /// + public ushort ChannelCount; + + /// + /// The maximum delay time in milliseconds. + /// + public uint DelayTimeMax; + + /// + /// The delay time in milliseconds. + /// + public uint DelayTime; + + /// + /// The target sample rate. (Q15) + /// + public uint SampleRate; + + /// + /// The input gain. (Q15) + /// + public uint InGain; + + /// + /// The feedback gain. (Q15) + /// + public uint FeedbackGain; + + /// + /// The output gain. (Q15) + /// + public uint OutGain; + + /// + /// The dry gain. (Q15) + /// + public uint DryGain; + + /// + /// The channel spread of the . (Q15) + /// + public uint ChannelSpread; + + /// + /// The low pass amount. (Q15) + /// + public uint LowPassAmount; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState Status; + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public bool IsChannelCountValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCount); + } + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public bool IsChannelCountMaxValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterParameter.cs new file mode 100644 index 00000000..0bce94a2 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterParameter.cs @@ -0,0 +1,138 @@ +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LimiterParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// The maximum number of channels supported. + /// + public ushort ChannelCountMax; + + /// + /// The total channel count used. + /// + public ushort ChannelCount; + + /// + /// The target sample rate. + /// + /// This is in kHz. + public int SampleRate; + + /// + /// The look ahead max time. + /// This is in microseconds. + /// + public int LookAheadTimeMax; + + /// + /// The attack time. + /// This is in microseconds. + /// + public int AttackTime; + + /// + /// The release time. + /// This is in microseconds. + /// + public int ReleaseTime; + + /// + /// The look ahead time. + /// This is in microseconds. + /// + public int LookAheadTime; + + /// + /// The attack coefficient. + /// + public float AttackCoefficient; + + /// + /// The release coefficient. + /// + public float ReleaseCoefficient; + + /// + /// The threshold. + /// + public float Threshold; + + /// + /// The input gain. + /// + public float InputGain; + + /// + /// The output gain. + /// + public float OutputGain; + + /// + /// The minimum samples stored in the delay buffer. + /// + public int DelayBufferSampleCountMin; + + /// + /// The maximum samples stored in the delay buffer. + /// + public int DelayBufferSampleCountMax; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState Status; + + /// + /// Indicate if the limiter effect should output statistics. + /// + [MarshalAs(UnmanagedType.I1)] + public bool StatisticsEnabled; + + /// + /// Indicate to the DSP that the user did a statistics reset. + /// + [MarshalAs(UnmanagedType.I1)] + public bool StatisticsReset; + + /// + /// Reserved/padding. + /// + private byte _reserved; + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public bool IsChannelCountValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCount); + } + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public bool IsChannelCountMaxValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterStatistics.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterStatistics.cs new file mode 100644 index 00000000..f353f18d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/LimiterStatistics.cs @@ -0,0 +1,31 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// Effect result state for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LimiterStatistics + { + /// + /// The max input sample value recorded by the limiter. + /// + public Array6 InputMax; + + /// + /// Compression gain min value. + /// + public Array6 CompressionGainMin; + + /// + /// Reset the statistics. + /// + public void Reset() + { + InputMax.AsSpan().Fill(0.0f); + CompressionGainMin.AsSpan().Fill(1.0f); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs new file mode 100644 index 00000000..c78ce595 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs @@ -0,0 +1,127 @@ +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct Reverb3dParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// The maximum number of channels supported. + /// + public ushort ChannelCountMax; + + /// + /// The total channel count used. + /// + public ushort ChannelCount; + + /// + /// Reserved/unused. + /// + private uint _reserved; + + /// + /// The target sample rate. + /// + /// This is in kHz. + public uint SampleRate; + + /// + /// Gain of the room high-frequency effect. + /// + public float RoomHf; + + /// + /// Reference high frequency. + /// + public float HfReference; + + /// + /// Reverberation decay time at low frequencies. + /// + public float DecayTime; + + /// + /// Ratio of the decay time at high frequencies to the decay time at low frequencies. + /// + public float HfDecayRatio; + + /// + /// Gain of the room effect. + /// + public float RoomGain; + + /// + /// Gain of the early reflections relative to . + /// + public float ReflectionsGain; + + /// + /// Gain of the late reverberation relative to . + /// + public float ReverbGain; + + /// + /// Echo density in the late reverberation decay. + /// + public float Diffusion; + + /// + /// Modal density in the late reverberation decay. + /// + public float ReflectionDelay; + + /// + /// Time limit between the early reflections and the late reverberation relative to the time of the first reflection. + /// + public float ReverbDelayTime; + + /// + /// Modal density in the late reverberation decay. + /// + public float Density; + + /// + /// The dry gain. + /// + public float DryGain; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState ParameterStatus; + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public bool IsChannelCountValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCount); + } + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public bool IsChannelCountMaxValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs new file mode 100644 index 00000000..baf049fb --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs @@ -0,0 +1,119 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Effect +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct ReverbParameter + { + /// + /// The input channel indices that will be used by the . + /// + public Array6 Input; + + /// + /// The output channel indices that will be used by the . + /// + public Array6 Output; + + /// + /// The maximum number of channels supported. + /// + public ushort ChannelCountMax; + + /// + /// The total channel count used. + /// + public ushort ChannelCount; + + /// + /// The target sample rate. (Q15) + /// + /// This is in kHz. + public int SampleRate; + + /// + /// The early mode to use. + /// + public ReverbEarlyMode EarlyMode; + + /// + /// The gain to apply to the result of the early reflection. (Q15) + /// + public int EarlyGain; + + /// + /// The pre-delay time in milliseconds. (Q15) + /// + public int PreDelayTime; + + /// + /// The late mode to use. + /// + public ReverbLateMode LateMode; + + /// + /// The gain to apply to the result of the late reflection. (Q15) + /// + public int LateGain; + + /// + /// The decay time. (Q15) + /// + public int DecayTime; + + /// + /// The high frequency decay ratio. (Q15) + /// + /// If >= 0.995f, it is considered disabled. + public int HighFrequencyDecayRatio; + + /// + /// The coloration of the decay. (Q15) + /// + public int Coloration; + + /// + /// The reverb gain. (Q15) + /// + public int ReverbGain; + + /// + /// The output gain. (Q15) + /// + public int OutGain; + + /// + /// The dry gain. (Q15) + /// + public int DryGain; + + /// + /// The current usage status of the effect on the client side. + /// + public UsageState Status; + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public bool IsChannelCountValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCount); + } + + /// + /// Check if the is valid. + /// + /// Returns true if the is valid. + public bool IsChannelCountMaxValid() + { + return EffectInParameterVersion1.IsChannelCountValid(ChannelCountMax); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion1.cs b/src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion1.cs new file mode 100644 index 00000000..e5419f70 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion1.cs @@ -0,0 +1,97 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for an effect version 1. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EffectInParameterVersion1 : IEffectInParameter + { + /// + /// Type of the effect. + /// + public EffectType Type; + + /// + /// Set to true if the effect is new. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsNew; + + /// + /// Set to true if the effect must be active. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsEnabled; + + /// + /// Reserved/padding. + /// + private byte _reserved1; + + /// + /// The target mix id of the effect. + /// + public int MixId; + + /// + /// Address of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + public ulong BufferBase; + + /// + /// Size of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + public ulong BufferSize; + + /// + /// Position of the effect while processing effects. + /// + public uint ProcessingOrder; + + /// + /// Reserved/padding. + /// + private uint _reserved2; + + /// + /// Specific data storage. + /// + private SpecificDataStruct _specificDataStart; + + [StructLayout(LayoutKind.Sequential, Size = 0xA0, Pack = 1)] + private struct SpecificDataStruct { } + + public Span SpecificData => SpanHelpers.AsSpan(ref _specificDataStart); + + EffectType IEffectInParameter.Type => Type; + + bool IEffectInParameter.IsNew => IsNew; + + bool IEffectInParameter.IsEnabled => IsEnabled; + + int IEffectInParameter.MixId => MixId; + + ulong IEffectInParameter.BufferBase => BufferBase; + + ulong IEffectInParameter.BufferSize => BufferSize; + + uint IEffectInParameter.ProcessingOrder => ProcessingOrder; + + /// + /// Check if the given channel count is valid. + /// + /// The channel count to check + /// Returns true if the channel count is valid. + public static bool IsChannelCountValid(int channelCount) + { + return channelCount == 1 || channelCount == 2 || channelCount == 4 || channelCount == 6; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion2.cs b/src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion2.cs new file mode 100644 index 00000000..250012d1 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/EffectInParameterVersion2.cs @@ -0,0 +1,97 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for an effect version 2. (added with REV9) + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EffectInParameterVersion2 : IEffectInParameter + { + /// + /// Type of the effect. + /// + public EffectType Type; + + /// + /// Set to true if the effect is new. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsNew; + + /// + /// Set to true if the effect must be active. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsEnabled; + + /// + /// Reserved/padding. + /// + private byte _reserved1; + + /// + /// The target mix id of the effect. + /// + public int MixId; + + /// + /// Address of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + public ulong BufferBase; + + /// + /// Size of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + public ulong BufferSize; + + /// + /// Position of the effect while processing effects. + /// + public uint ProcessingOrder; + + /// + /// Reserved/padding. + /// + private uint _reserved2; + + /// + /// Specific data storage. + /// + private SpecificDataStruct _specificDataStart; + + [StructLayout(LayoutKind.Sequential, Size = 0xA0, Pack = 1)] + private struct SpecificDataStruct { } + + public Span SpecificData => SpanHelpers.AsSpan(ref _specificDataStart); + + EffectType IEffectInParameter.Type => Type; + + bool IEffectInParameter.IsNew => IsNew; + + bool IEffectInParameter.IsEnabled => IsEnabled; + + int IEffectInParameter.MixId => MixId; + + ulong IEffectInParameter.BufferBase => BufferBase; + + ulong IEffectInParameter.BufferSize => BufferSize; + + uint IEffectInParameter.ProcessingOrder => ProcessingOrder; + + /// + /// Check if the given channel count is valid. + /// + /// The channel count to check + /// Returns true if the channel count is valid. + public static bool IsChannelCountValid(int channelCount) + { + return channelCount == 1 || channelCount == 2 || channelCount == 4 || channelCount == 6; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion1.cs b/src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion1.cs new file mode 100644 index 00000000..5e6a33ac --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion1.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information for an effect version 1. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EffectOutStatusVersion1 : IEffectOutStatus + { + /// + /// Current effect state. + /// + public EffectState State; + + /// + /// Unused/Reserved. + /// + private unsafe fixed byte _reserved[15]; + + EffectState IEffectOutStatus.State { get => State; set => State = value; } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion2.cs b/src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion2.cs new file mode 100644 index 00000000..f2c9768b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/EffectOutStatusVersion2.cs @@ -0,0 +1,28 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information for an effect version 2. (added with REV9) + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EffectOutStatusVersion2 : IEffectOutStatus + { + /// + /// Current effect state. + /// + public EffectState State; + + /// + /// Unused/Reserved. + /// + private unsafe fixed byte _reserved[15]; + + /// + /// Current result state. + /// + public EffectResultState ResultState; + + EffectState IEffectOutStatus.State { get => State; set => State = value; } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/EffectResultState.cs b/src/Ryujinx.Audio/Renderer/Parameter/EffectResultState.cs new file mode 100644 index 00000000..bd96c22b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/EffectResultState.cs @@ -0,0 +1,26 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Effect result state (added in REV9). + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct EffectResultState + { + /// + /// Specific data storage. + /// + private SpecificDataStruct _specificDataStart; + + [StructLayout(LayoutKind.Sequential, Size = 0x80, Pack = 1)] + private struct SpecificDataStruct { } + + /// + /// Specific data changing depending of the type of effect. See also the namespace. + /// + public Span SpecificData => SpanHelpers.AsSpan(ref _specificDataStart); + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/EffectState.cs b/src/Ryujinx.Audio/Renderer/Parameter/EffectState.cs new file mode 100644 index 00000000..911ba6d8 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/EffectState.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// The state of an effect. + /// + public enum EffectState : byte + { + /// + /// The effect is enabled. + /// + Enabled = 3, + + /// + /// The effect is disabled. + /// + Disabled = 4 + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/IEffectInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/IEffectInParameter.cs new file mode 100644 index 00000000..bdd1ca45 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/IEffectInParameter.cs @@ -0,0 +1,53 @@ +using Ryujinx.Audio.Renderer.Common; +using System; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Generic interface to represent input information for an effect. + /// + public interface IEffectInParameter + { + /// + /// Type of the effect. + /// + EffectType Type { get; } + + /// + /// Set to true if the effect is new. + /// + bool IsNew { get; } + + /// + /// Set to true if the effect must be active. + /// + bool IsEnabled { get; } + + /// + /// The target mix id of the effect. + /// + int MixId { get; } + + /// + /// Address of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + ulong BufferBase { get; } + + /// + /// Size of the processing workbuffer. + /// + /// This is additional data that could be required by the effect processing. + ulong BufferSize { get; } + + /// + /// Position of the effect while processing effects. + /// + uint ProcessingOrder { get; } + + /// + /// Specific data changing depending of the . See also the namespace. + /// + Span SpecificData { get; } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/IEffectOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/IEffectOutStatus.cs new file mode 100644 index 00000000..a5addbcb --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/IEffectOutStatus.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Generic interface to represent output information for an effect. + /// + public interface IEffectOutStatus + { + /// + /// Current effect state. + /// + EffectState State { get; set; } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolInParameter.cs new file mode 100644 index 00000000..242e3843 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolInParameter.cs @@ -0,0 +1,33 @@ +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for a memory pool. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MemoryPoolInParameter + { + /// + /// The CPU address used by the memory pool. + /// + public CpuAddress CpuAddress; + + /// + /// The size used by the memory pool. + /// + public ulong Size; + + /// + /// The target state the user wants. + /// + public MemoryPoolUserState State; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[3]; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolOutStatus.cs new file mode 100644 index 00000000..29a6e261 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/MemoryPoolOutStatus.cs @@ -0,0 +1,22 @@ +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information for a memory pool. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MemoryPoolOutStatus + { + /// + /// The current server memory pool state. + /// + public MemoryPoolUserState State; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[3]; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs b/src/Ryujinx.Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs new file mode 100644 index 00000000..c0954cda --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs @@ -0,0 +1,27 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information header for mix updates on REV7 and later + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MixInParameterDirtyOnlyUpdate + { + /// + /// Magic of the header + /// + /// Never checked on hardware. + public uint Magic; + + /// + /// The count of following this header. + /// + public uint MixCount; + + /// + /// Reserved/unused. + /// + private unsafe fixed byte _reserved[24]; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/MixParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/MixParameter.cs new file mode 100644 index 00000000..5b9a969a --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/MixParameter.cs @@ -0,0 +1,95 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for a mix. + /// + /// Also used on the client side for mix tracking. + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct MixParameter + { + /// + /// Base volume of the mix. + /// + public float Volume; + + /// + /// Target sample rate of the mix. + /// + public uint SampleRate; + + /// + /// Target buffer count. + /// + public uint BufferCount; + + /// + /// Set to true if in use. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// Set to true if it was changed. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsDirty; + + /// + /// Reserved/padding. + /// + private ushort _reserved1; + + /// + /// The id of the mix. + /// + public int MixId; + + /// + /// The effect count. (client side) + /// + public uint EffectCount; + + /// + /// The mix node id. + /// + public int NodeId; + + /// + /// Reserved/padding. + /// + private ulong _reserved2; + + /// + /// Mix buffer volumes storage. + /// + private MixVolumeArray _mixBufferVolumeArray; + + /// + /// The mix to output the result of this mix. + /// + public int DestinationMixId; + + /// + /// The splitter to output the result of this mix. + /// + public uint DestinationSplitterId; + + /// + /// Reserved/padding. + /// + private uint _reserved3; + + [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax * Constants.MixBufferCountMax, Pack = 1)] + private struct MixVolumeArray { } + + /// + /// Mix buffer volumes. + /// + /// Used when no splitter id is specified. + public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mixBufferVolumeArray); + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceInParameter.cs new file mode 100644 index 00000000..0f9a3aa3 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceInParameter.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Performance +{ + /// + /// Input information for performance monitoring. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PerformanceInParameter + { + /// + /// The target node id to monitor performance on. + /// + public int TargetNodeId; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[3]; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceOutStatus.cs new file mode 100644 index 00000000..64bbe080 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceOutStatus.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Performance +{ + /// + /// Output information for performance monitoring. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct PerformanceOutStatus + { + /// + /// Indicates the total size output to the performance buffer. + /// + public uint HistorySize; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[3]; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/RendererInfoOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/RendererInfoOutStatus.cs new file mode 100644 index 00000000..a42ea833 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/RendererInfoOutStatus.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Renderer output information on REV5 and later. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RendererInfoOutStatus + { + /// + /// The count of updates sent to the . + /// + public ulong ElapsedFrameCount; + + /// + /// Reserved/Unused. + /// + private ulong _reserved; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Sink/CircularBufferParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Sink/CircularBufferParameter.cs new file mode 100644 index 00000000..7c02d65f --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Sink/CircularBufferParameter.cs @@ -0,0 +1,62 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Parameter.Sink +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct CircularBufferParameter + { + /// + /// The CPU address of the user circular buffer. + /// + public CpuAddress BufferAddress; + + /// + /// The size of the user circular buffer. + /// + public uint BufferSize; + + /// + /// The total count of channels to output to the circular buffer. + /// + public uint InputCount; + + /// + /// The target sample count to output per update in the circular buffer. + /// + public uint SampleCount; + + /// + /// Last read offset on the CPU side. + /// + public uint LastReadOffset; + + /// + /// The target . + /// + /// Only is supported. + public SampleFormat SampleFormat; + + /// + /// Reserved/padding. + /// + private unsafe fixed byte _reserved1[3]; + + /// + /// The input channels index that will be used. + /// + public Array6 Input; + + /// + /// Reserved/padding. + /// + private ushort _reserved2; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Sink/DeviceParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/Sink/DeviceParameter.cs new file mode 100644 index 00000000..abeadacc --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/Sink/DeviceParameter.cs @@ -0,0 +1,58 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter.Sink +{ + /// + /// for . + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct DeviceParameter + { + /// + /// Device name storage. + /// + private DeviceNameStruct _deviceName; + + /// + /// Reserved/padding. + /// + private byte _padding; + + /// + /// The total count of channels to output to the device. + /// + public uint InputCount; + + /// + /// The input channels index that will be used. + /// + public Array6 Input; + + /// + /// Reserved/padding. + /// + private byte _reserved; + + /// + /// Set to true if the user controls Surround to Stereo downmixing coefficients. + /// + [MarshalAs(UnmanagedType.I1)] + public bool DownMixParameterEnabled; + + /// + /// The user Surround to Stereo downmixing coefficients. + /// + public Array4 DownMixParameter; + + [StructLayout(LayoutKind.Sequential, Size = 0xFF, Pack = 1)] + private struct DeviceNameStruct { } + + /// + /// The output device name. + /// + public Span DeviceName => SpanHelpers.AsSpan(ref _deviceName); + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SinkInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/SinkInParameter.cs new file mode 100644 index 00000000..1ee4eb53 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/SinkInParameter.cs @@ -0,0 +1,53 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for a sink. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SinkInParameter + { + /// + /// Type of the sink. + /// + public SinkType Type; + + /// + /// Set to true if the sink is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// Reserved/padding. + /// + private ushort _reserved1; + + /// + /// The node id of the sink. + /// + public int NodeId; + + /// + /// Reserved/padding. + /// + private unsafe fixed ulong _reserved2[3]; + + /// + /// Specific data storage. + /// + private SpecificDataStruct _specificDataStart; + + [StructLayout(LayoutKind.Sequential, Size = 0x120, Pack = 1)] + private struct SpecificDataStruct { } + + /// + /// Specific data changing depending of the . See also the namespace. + /// + public Span SpecificData => SpanHelpers.AsSpan(ref _specificDataStart); + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SinkOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/SinkOutStatus.cs new file mode 100644 index 00000000..426b861c --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/SinkOutStatus.cs @@ -0,0 +1,26 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information for a sink. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SinkOutStatus + { + /// + /// Last written offset if the sink type is . + /// + public uint LastWrittenOffset; + + /// + /// Reserved/padding. + /// + private uint _padding; + + /// + /// Reserved/padding. + /// + private unsafe fixed ulong _reserved[3]; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs new file mode 100644 index 00000000..96c43092 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs @@ -0,0 +1,67 @@ +using Ryujinx.Common.Utilities; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input header for a splitter destination update. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SplitterDestinationInParameter + { + /// + /// Magic of the input header. + /// + public uint Magic; + + /// + /// Target splitter destination data id. + /// + public int Id; + + /// + /// Mix buffer volumes storage. + /// + private MixArray _mixBufferVolume; + + /// + /// The mix to output the result of the splitter. + /// + public int DestinationId; + + /// + /// Set to true if in use. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// Reserved/padding. + /// + private unsafe fixed byte _reserved[3]; + + [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)] + private struct MixArray { } + + /// + /// Mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mixBufferVolume); + + /// + /// The expected constant of any input header. + /// + private const uint ValidMagic = 0x44444E53; + + /// + /// Check if the magic is valid. + /// + /// Returns true if the magic is valid. + public bool IsMagicValid() + { + return Magic == ValidMagic; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameter.cs new file mode 100644 index 00000000..0220497d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameter.cs @@ -0,0 +1,46 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input header for a splitter state update. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SplitterInParameter + { + /// + /// Magic of the input header. + /// + public uint Magic; + + /// + /// Target splitter id. + /// + public int Id; + + /// + /// Target sample rate to use on the splitter. + /// + public uint SampleRate; + + /// + /// Count of splitter destinations. + /// + /// Splitter destination ids are defined right after this header. + public int DestinationCount; + + /// + /// The expected constant of any input header. + /// + private const uint ValidMagic = 0x49444E53; + + /// + /// Check if the magic is valid. + /// + /// Returns true if the magic is valid. + public bool IsMagicValid() + { + return Magic == ValidMagic; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameterHeader.cs b/src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameterHeader.cs new file mode 100644 index 00000000..dbae17a9 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/SplitterInParameterHeader.cs @@ -0,0 +1,45 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input header for splitter update. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SplitterInParameterHeader + { + /// + /// Magic of the input header. + /// + public uint Magic; + + /// + /// The count of after the header. + /// + public uint SplitterCount; + + /// + /// The count of splitter destinations after the header and splitter info. + /// + public uint SplitterDestinationCount; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved[5]; + + /// + /// The expected constant of any input splitter header. + /// + private const uint ValidMagic = 0x48444E53; + + /// + /// Check if the magic is valid. + /// + /// Returns true if the magic is valid. + public bool IsMagicValid() + { + return Magic == ValidMagic; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/VoiceChannelResourceInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/VoiceChannelResourceInParameter.cs new file mode 100644 index 00000000..6a863237 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/VoiceChannelResourceInParameter.cs @@ -0,0 +1,28 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for a voice channel resources. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x70, Pack = 1)] + public struct VoiceChannelResourceInParameter + { + /// + /// The id of the voice channel resource. + /// + public uint Id; + + /// + /// Mix volumes for the voice channel resource. + /// + public Array24 Mix; + + /// + /// Indicate if the voice channel resource is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs new file mode 100644 index 00000000..c4b4ba31 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs @@ -0,0 +1,344 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp; +using Ryujinx.Common.Memory; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Input information for a voice. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x170, Pack = 1)] + public struct VoiceInParameter + { + /// + /// Id of the voice. + /// + public int Id; + + /// + /// Node id of the voice. + /// + public int NodeId; + + /// + /// Set to true if the voice is new. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsNew; + + /// + /// Set to true if the voice is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool InUse; + + /// + /// The voice wanted by the user. + /// + public PlayState PlayState; + + /// + /// The of the voice. + /// + public SampleFormat SampleFormat; + + /// + /// The sample rate of the voice. + /// + public uint SampleRate; + + /// + /// The priority of the voice. + /// + public uint Priority; + + /// + /// Target sorting position of the voice. (Used to sort voices with the same ) + /// + public uint SortingOrder; + + /// + /// The total channel count used. + /// + public uint ChannelCount; + + /// + /// The pitch used on the voice. + /// + public float Pitch; + + /// + /// The output volume of the voice. + /// + public float Volume; + + /// + /// Biquad filters to apply to the output of the voice. + /// + public Array2 BiquadFilters; + + /// + /// Total count of of the voice. + /// + public uint WaveBuffersCount; + + /// + /// Current playing of the voice. + /// + public uint WaveBuffersIndex; + + /// + /// Reserved/unused. + /// + private uint _reserved1; + + /// + /// User state address required by the data source. + /// + /// Only used for as the address of the GC-ADPCM coefficients. + public ulong DataSourceStateAddress; + + /// + /// User state size required by the data source. + /// + /// Only used for as the size of the GC-ADPCM coefficients. + public ulong DataSourceStateSize; + + /// + /// The target mix id of the voice. + /// + public int MixId; + + /// + /// The target splitter id of the voice. + /// + public uint SplitterId; + + /// + /// The wavebuffer parameters of this voice. + /// + public Array4 WaveBuffers; + + /// + /// The channel resource ids associated to the voice. + /// + public Array6 ChannelResourceIds; + + /// + /// Reset the voice drop flag during voice server update. + /// + [MarshalAs(UnmanagedType.I1)] + public bool ResetVoiceDropFlag; + + /// + /// Flush the amount of wavebuffer specified. This will result in the wavebuffer being skipped and marked played. + /// + /// This was added on REV5. + public byte FlushWaveBufferCount; + + /// + /// Reserved/unused. + /// + private ushort _reserved2; + + /// + /// Change the behaviour of the voice. + /// + /// This was added on REV5. + public DecodingBehaviour DecodingBehaviourFlags; + + /// + /// Change the Sample Rate Conversion (SRC) quality of the voice. + /// + /// This was added on REV8. + public SampleRateConversionQuality SrcQuality; + + /// + /// This was previously used for opus codec support on the Audio Renderer and was removed on REV3. + /// + public uint ExternalContext; + + /// + /// This was previously used for opus codec support on the Audio Renderer and was removed on REV3. + /// + public uint ExternalContextSize; + + /// + /// Reserved/unused. + /// + private unsafe fixed uint _reserved3[2]; + + /// + /// Input information for a voice wavebuffer. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x38, Pack = 1)] + public struct WaveBufferInternal + { + /// + /// Address of the wavebuffer data. + /// + public ulong Address; + + /// + /// Size of the wavebuffer data. + /// + public ulong Size; + + /// + /// Offset of the first sample to play. + /// + public uint StartSampleOffset; + + /// + /// Offset of the last sample to play. + /// + public uint EndSampleOffset; + + /// + /// If set to true, the wavebuffer will loop when reaching . + /// + /// + /// Starting with REV8, you can specify how many times to loop the wavebuffer () and where it should start and end when looping ( and ) + /// + [MarshalAs(UnmanagedType.I1)] + public bool ShouldLoop; + + /// + /// Indicates that this is the last wavebuffer to play of the voice. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsEndOfStream; + + /// + /// Indicates if the server should update its internal state. + /// + [MarshalAs(UnmanagedType.I1)] + public bool SentToServer; + + /// + /// Reserved/unused. + /// + private byte _reserved; + + /// + /// If set to anything other than 0, specifies how many times to loop the wavebuffer. + /// + /// This was added in REV8. + public int LoopCount; + + /// + /// Address of the context used by the sample decoder. + /// + /// This is only currently used by . + public ulong ContextAddress; + + /// + /// Size of the context used by the sample decoder. + /// + /// This is only currently used by . + public ulong ContextSize; + + /// + /// If set to anything other than 0, specifies the offset of the first sample to play when looping. + /// + /// This was added in REV8. + public uint LoopFirstSampleOffset; + + /// + /// If set to anything other than 0, specifies the offset of the last sample to play when looping. + /// + /// This was added in REV8. + public uint LoopLastSampleOffset; + + /// + /// Check if the sample offsets are in a valid range for generic PCM. + /// + /// The PCM sample type + /// Returns true if the sample offset are in range of the size. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsSampleOffsetInRangeForPcm() where T : unmanaged + { + uint dataTypeSize = (uint)Unsafe.SizeOf(); + + return StartSampleOffset * dataTypeSize <= Size && + EndSampleOffset * dataTypeSize <= Size; + } + + /// + /// Check if the sample offsets are in a valid range for the given . + /// + /// The target + /// Returns true if the sample offset are in range of the size. + public bool IsSampleOffsetValid(SampleFormat format) + { + bool result; + + switch (format) + { + case SampleFormat.PcmInt16: + result = IsSampleOffsetInRangeForPcm(); + break; + case SampleFormat.PcmFloat: + result = IsSampleOffsetInRangeForPcm(); + break; + case SampleFormat.Adpcm: + result = AdpcmHelper.GetAdpcmDataSize((int)StartSampleOffset) <= Size && + AdpcmHelper.GetAdpcmDataSize((int)EndSampleOffset) <= Size; + break; + default: + throw new NotImplementedException($"{format} not implemented!"); + } + + return result; + } + } + + /// + /// Flag altering the behaviour of wavebuffer decoding. + /// + [Flags] + public enum DecodingBehaviour : ushort + { + /// + /// Default decoding behaviour. + /// + Default = 0, + + /// + /// Reset the played samples accumulator when looping. + /// + PlayedSampleCountResetWhenLooping = 1, + + /// + /// Skip pitch and Sample Rate Conversion (SRC). + /// + SkipPitchAndSampleRateConversion = 2 + } + + /// + /// Specify the quality to use during Sample Rate Conversion (SRC) and pitch handling. + /// + /// This was added in REV8. + public enum SampleRateConversionQuality : byte + { + /// + /// Resample interpolating 4 samples per output sample. + /// + Default, + + /// + /// Resample interpolating 8 samples per output sample. + /// + High, + + /// + /// Resample interpolating 1 samples per output sample. + /// + Low + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Parameter/VoiceOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/VoiceOutStatus.cs new file mode 100644 index 00000000..be9d3584 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Parameter/VoiceOutStatus.cs @@ -0,0 +1,35 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Parameter +{ + /// + /// Output information about a voice. + /// + /// See + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct VoiceOutStatus + { + /// + /// The total amount of samples that was played. + /// + /// This is reset to 0 when a finishes playing and is set. + /// This is reset to 0 when looping while is set. + public ulong PlayedSampleCount; + + /// + /// The total amount of consumed. + /// + public uint PlayedWaveBuffersCount; + + /// + /// If set to true, the voice was dropped. + /// + [MarshalAs(UnmanagedType.I1)] + public bool VoiceDropFlag; + + /// + /// Reserved/unused. + /// + private unsafe fixed byte _reserved[3]; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs new file mode 100644 index 00000000..53570243 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs @@ -0,0 +1,893 @@ +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.Command; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using Ryujinx.Audio.Renderer.Server.Mix; +using Ryujinx.Audio.Renderer.Server.Performance; +using Ryujinx.Audio.Renderer.Server.Sink; +using Ryujinx.Audio.Renderer.Server.Splitter; +using Ryujinx.Audio.Renderer.Server.Types; +using Ryujinx.Audio.Renderer.Server.Upsampler; +using Ryujinx.Audio.Renderer.Server.Voice; +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Buffers; +using System.Diagnostics; +using System.Threading; + +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server +{ + public class AudioRenderSystem : IDisposable + { + private object _lock = new object(); + + private AudioRendererRenderingDevice _renderingDevice; + private AudioRendererExecutionMode _executionMode; + private IWritableEvent _systemEvent; + private ManualResetEvent _terminationEvent; + private MemoryPoolState _dspMemoryPoolState; + private VoiceContext _voiceContext; + private MixContext _mixContext; + private SinkContext _sinkContext; + private SplitterContext _splitterContext; + private EffectContext _effectContext; + private PerformanceManager _performanceManager; + private UpsamplerManager _upsamplerManager; + private bool _isActive; + private BehaviourContext _behaviourContext; + private ulong _totalElapsedTicksUpdating; + private ulong _totalElapsedTicks; + private int _sessionId; + private Memory _memoryPools; + + private uint _sampleRate; + private uint _sampleCount; + private uint _mixBufferCount; + private uint _voiceChannelCountMax; + private uint _upsamplerCount; + private uint _memoryPoolCount; + private uint _processHandle; + private ulong _appletResourceId; + + private MemoryHandle _workBufferMemoryPin; + + private Memory _mixBuffer; + private Memory _depopBuffer; + + private uint _renderingTimeLimitPercent; + private bool _voiceDropEnabled; + private uint _voiceDropCount; + private float _voiceDropParameter; + private bool _isDspRunningBehind; + + private ICommandProcessingTimeEstimator _commandProcessingTimeEstimator; + + private Memory _performanceBuffer; + + public IVirtualMemoryManager MemoryManager { get; private set; } + + private ulong _elapsedFrameCount; + private ulong _renderingStartTick; + + private AudioRendererManager _manager; + + private int _disposeState; + + public AudioRenderSystem(AudioRendererManager manager, IWritableEvent systemEvent) + { + _manager = manager; + _terminationEvent = new ManualResetEvent(false); + _dspMemoryPoolState = MemoryPoolState.Create(MemoryPoolState.LocationType.Dsp); + _voiceContext = new VoiceContext(); + _mixContext = new MixContext(); + _sinkContext = new SinkContext(); + _splitterContext = new SplitterContext(); + _effectContext = new EffectContext(); + + _commandProcessingTimeEstimator = null; + _systemEvent = systemEvent; + _behaviourContext = new BehaviourContext(); + + _totalElapsedTicksUpdating = 0; + _sessionId = 0; + _voiceDropParameter = 1.0f; + } + + public ResultCode Initialize( + ref AudioRendererConfiguration parameter, + uint processHandle, + Memory workBufferMemory, + CpuAddress workBuffer, + ulong workBufferSize, + int sessionId, + ulong appletResourceId, + IVirtualMemoryManager memoryManager) + { + if (!BehaviourContext.CheckValidRevision(parameter.Revision)) + { + return ResultCode.OperationFailed; + } + + if (GetWorkBufferSize(ref parameter) > workBufferSize) + { + return ResultCode.WorkBufferTooSmall; + } + + Debug.Assert(parameter.RenderingDevice == AudioRendererRenderingDevice.Dsp && parameter.ExecutionMode == AudioRendererExecutionMode.Auto); + + Logger.Info?.Print(LogClass.AudioRenderer, $"Initializing with REV{BehaviourContext.GetRevisionNumber(parameter.Revision)}"); + + _behaviourContext.SetUserRevision(parameter.Revision); + + _sampleRate = parameter.SampleRate; + _sampleCount = parameter.SampleCount; + _mixBufferCount = parameter.MixBufferCount; + _voiceChannelCountMax = Constants.VoiceChannelCountMax; + _upsamplerCount = parameter.SinkCount + parameter.SubMixBufferCount; + _appletResourceId = appletResourceId; + _memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * Constants.VoiceWaveBufferCount; + _renderingDevice = parameter.RenderingDevice; + _executionMode = parameter.ExecutionMode; + _sessionId = sessionId; + MemoryManager = memoryManager; + + if (memoryManager is IRefCounted rc) + { + rc.IncrementReferenceCount(); + } + + WorkBufferAllocator workBufferAllocator; + + workBufferMemory.Span.Fill(0); + _workBufferMemoryPin = workBufferMemory.Pin(); + + workBufferAllocator = new WorkBufferAllocator(workBufferMemory); + + PoolMapper poolMapper = new PoolMapper(processHandle, false); + poolMapper.InitializeSystemPool(ref _dspMemoryPoolState, workBuffer, workBufferSize); + + _mixBuffer = workBufferAllocator.Allocate(_sampleCount * (_voiceChannelCountMax + _mixBufferCount), 0x10); + + if (_mixBuffer.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + Memory upSamplerWorkBuffer = workBufferAllocator.Allocate(Constants.TargetSampleCount * (_voiceChannelCountMax + _mixBufferCount) * _upsamplerCount, 0x10); + + if (upSamplerWorkBuffer.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + _depopBuffer = workBufferAllocator.Allocate(BitUtils.AlignUp(parameter.MixBufferCount, Constants.BufferAlignment), Constants.BufferAlignment); + + if (_depopBuffer.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + // Invalidate DSP cache on what was currently allocated with workBuffer. + AudioProcessorMemoryManager.InvalidateDspCache(_dspMemoryPoolState.Translate(workBuffer, workBufferAllocator.Offset), workBufferAllocator.Offset); + + Debug.Assert((workBufferAllocator.Offset % Constants.BufferAlignment) == 0); + + Memory voices = workBufferAllocator.Allocate(parameter.VoiceCount, VoiceState.Alignment); + + if (voices.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + foreach (ref VoiceState voice in voices.Span) + { + voice.Initialize(); + } + + // A pain to handle as we can't have VoiceState*, use indices to be a bit more safe + Memory sortedVoices = workBufferAllocator.Allocate(parameter.VoiceCount, 0x10); + + if (sortedVoices.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + // Clear memory (use -1 as it's an invalid index) + sortedVoices.Span.Fill(-1); + + Memory voiceChannelResources = workBufferAllocator.Allocate(parameter.VoiceCount, VoiceChannelResource.Alignment); + + if (voiceChannelResources.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + for (uint id = 0; id < voiceChannelResources.Length; id++) + { + ref VoiceChannelResource voiceChannelResource = ref voiceChannelResources.Span[(int)id]; + + voiceChannelResource.Id = id; + voiceChannelResource.IsUsed = false; + } + + Memory voiceUpdateStates = workBufferAllocator.Allocate(parameter.VoiceCount, VoiceUpdateState.Align); + + if (voiceUpdateStates.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + uint mixesCount = parameter.SubMixBufferCount + 1; + + Memory mixes = workBufferAllocator.Allocate(mixesCount, MixState.Alignment); + + if (mixes.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + if (parameter.EffectCount == 0) + { + foreach (ref MixState mix in mixes.Span) + { + mix = new MixState(Memory.Empty, ref _behaviourContext); + } + } + else + { + Memory effectProcessingOrderArray = workBufferAllocator.Allocate(parameter.EffectCount * mixesCount, 0x10); + + foreach (ref MixState mix in mixes.Span) + { + mix = new MixState(effectProcessingOrderArray.Slice(0, (int)parameter.EffectCount), ref _behaviourContext); + + effectProcessingOrderArray = effectProcessingOrderArray.Slice((int)parameter.EffectCount); + } + } + + // Initialize the final mix id + mixes.Span[0].MixId = Constants.FinalMixId; + + Memory sortedMixesState = workBufferAllocator.Allocate(mixesCount, 0x10); + + if (sortedMixesState.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + // Clear memory (use -1 as it's an invalid index) + sortedMixesState.Span.Fill(-1); + + Memory nodeStatesWorkBuffer = Memory.Empty; + Memory edgeMatrixWorkBuffer = Memory.Empty; + + if (_behaviourContext.IsSplitterSupported()) + { + nodeStatesWorkBuffer = workBufferAllocator.Allocate((uint)NodeStates.GetWorkBufferSize((int)mixesCount), 1); + edgeMatrixWorkBuffer = workBufferAllocator.Allocate((uint)EdgeMatrix.GetWorkBufferSize((int)mixesCount), 1); + + if (nodeStatesWorkBuffer.IsEmpty || edgeMatrixWorkBuffer.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + } + + _mixContext.Initialize(sortedMixesState, mixes, nodeStatesWorkBuffer, edgeMatrixWorkBuffer); + + _memoryPools = workBufferAllocator.Allocate(_memoryPoolCount, MemoryPoolState.Alignment); + + if (_memoryPools.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + foreach (ref MemoryPoolState state in _memoryPools.Span) + { + state = MemoryPoolState.Create(MemoryPoolState.LocationType.Cpu); + } + + if (!_splitterContext.Initialize(ref _behaviourContext, ref parameter, workBufferAllocator)) + { + return ResultCode.WorkBufferTooSmall; + } + + _processHandle = processHandle; + + _upsamplerManager = new UpsamplerManager(upSamplerWorkBuffer, _upsamplerCount); + + _effectContext.Initialize(parameter.EffectCount, _behaviourContext.IsEffectInfoVersion2Supported() ? parameter.EffectCount : 0); + _sinkContext.Initialize(parameter.SinkCount); + + Memory voiceUpdateStatesDsp = workBufferAllocator.Allocate(parameter.VoiceCount, VoiceUpdateState.Align); + + if (voiceUpdateStatesDsp.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + _voiceContext.Initialize(sortedVoices, voices, voiceChannelResources, voiceUpdateStates, voiceUpdateStatesDsp, parameter.VoiceCount); + + if (parameter.PerformanceMetricFramesCount > 0) + { + ulong performanceBufferSize = PerformanceManager.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter, ref _behaviourContext) * (parameter.PerformanceMetricFramesCount + 1) + 0xC; + + _performanceBuffer = workBufferAllocator.Allocate(performanceBufferSize, Constants.BufferAlignment); + + if (_performanceBuffer.IsEmpty) + { + return ResultCode.WorkBufferTooSmall; + } + + _performanceManager = PerformanceManager.Create(_performanceBuffer, ref parameter, _behaviourContext); + } + else + { + _performanceManager = null; + } + + _totalElapsedTicksUpdating = 0; + _totalElapsedTicks = 0; + _renderingTimeLimitPercent = 100; + _voiceDropEnabled = parameter.VoiceDropEnabled && _executionMode == AudioRendererExecutionMode.Auto; + + AudioProcessorMemoryManager.InvalidateDataCache(workBuffer, workBufferSize); + + _processHandle = processHandle; + _elapsedFrameCount = 0; + _voiceDropParameter = 1.0f; + + switch (_behaviourContext.GetCommandProcessingTimeEstimatorVersion()) + { + case 1: + _commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion1(_sampleCount, _mixBufferCount); + break; + case 2: + _commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion2(_sampleCount, _mixBufferCount); + break; + case 3: + _commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion3(_sampleCount, _mixBufferCount); + break; + case 4: + _commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion4(_sampleCount, _mixBufferCount); + break; + case 5: + _commandProcessingTimeEstimator = new CommandProcessingTimeEstimatorVersion5(_sampleCount, _mixBufferCount); + break; + default: + throw new NotImplementedException($"Unsupported processing time estimator version {_behaviourContext.GetCommandProcessingTimeEstimatorVersion()}."); + } + + return ResultCode.Success; + } + + public void Start() + { + Logger.Info?.Print(LogClass.AudioRenderer, $"Starting renderer id {_sessionId}"); + + lock (_lock) + { + _elapsedFrameCount = 0; + _isActive = true; + } + } + + public void Stop() + { + Logger.Info?.Print(LogClass.AudioRenderer, $"Stopping renderer id {_sessionId}"); + + lock (_lock) + { + _isActive = false; + } + + if (_executionMode == AudioRendererExecutionMode.Auto) + { + _terminationEvent.WaitOne(); + } + + Logger.Info?.Print(LogClass.AudioRenderer, $"Stopped renderer id {_sessionId}"); + } + + public void Disable() + { + lock (_lock) + { + _isActive = false; + } + } + + public ResultCode Update(Memory output, Memory performanceOutput, ReadOnlyMemory input) + { + lock (_lock) + { + ulong updateStartTicks = GetSystemTicks(); + + output.Span.Fill(0); + + StateUpdater stateUpdater = new StateUpdater(input, output, _processHandle, _behaviourContext); + + ResultCode result; + + result = stateUpdater.UpdateBehaviourContext(); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateMemoryPools(_memoryPools.Span); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateVoiceChannelResources(_voiceContext); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateVoices(_voiceContext, _memoryPools); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateEffects(_effectContext, _isActive, _memoryPools); + + if (result != ResultCode.Success) + { + return result; + } + + if (_behaviourContext.IsSplitterSupported()) + { + result = stateUpdater.UpdateSplitter(_splitterContext); + + if (result != ResultCode.Success) + { + return result; + } + } + + result = stateUpdater.UpdateMixes(_mixContext, GetMixBufferCount(), _effectContext, _splitterContext); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateSinks(_sinkContext, _memoryPools); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdatePerformanceBuffer(_performanceManager, performanceOutput.Span); + + if (result != ResultCode.Success) + { + return result; + } + + result = stateUpdater.UpdateErrorInfo(); + + if (result != ResultCode.Success) + { + return result; + } + + if (_behaviourContext.IsElapsedFrameCountSupported()) + { + result = stateUpdater.UpdateRendererInfo(_elapsedFrameCount); + + if (result != ResultCode.Success) + { + return result; + } + } + + result = stateUpdater.CheckConsumedSize(); + + if (result != ResultCode.Success) + { + return result; + } + + _systemEvent.Clear(); + + ulong updateEndTicks = GetSystemTicks(); + + _totalElapsedTicksUpdating += (updateEndTicks - updateStartTicks); + + return result; + } + } + + private ulong GetSystemTicks() + { + return (ulong)(_manager.TickSource.ElapsedSeconds * Constants.TargetTimerFrequency); + } + + private uint ComputeVoiceDrop(CommandBuffer commandBuffer, uint voicesEstimatedTime, long deltaTimeDsp) + { + int i; + + for (i = 0; i < commandBuffer.CommandList.Commands.Count; i++) + { + ICommand command = commandBuffer.CommandList.Commands[i]; + + CommandType commandType = command.CommandType; + + if (commandType == CommandType.AdpcmDataSourceVersion1 || + commandType == CommandType.AdpcmDataSourceVersion2 || + commandType == CommandType.PcmInt16DataSourceVersion1 || + commandType == CommandType.PcmInt16DataSourceVersion2 || + commandType == CommandType.PcmFloatDataSourceVersion1 || + commandType == CommandType.PcmFloatDataSourceVersion2 || + commandType == CommandType.Performance) + { + break; + } + } + + uint voiceDropped = 0; + + for (; i < commandBuffer.CommandList.Commands.Count; i++) + { + ICommand targetCommand = commandBuffer.CommandList.Commands[i]; + + int targetNodeId = targetCommand.NodeId; + + if (voicesEstimatedTime <= deltaTimeDsp || NodeIdHelper.GetType(targetNodeId) != NodeIdType.Voice) + { + break; + } + + ref VoiceState voice = ref _voiceContext.GetState(NodeIdHelper.GetBase(targetNodeId)); + + if (voice.Priority == Constants.VoiceHighestPriority) + { + break; + } + + // We can safely drop this voice, disable all associated commands while activating depop preparation commands. + voiceDropped++; + voice.VoiceDropFlag = true; + + Logger.Warning?.Print(LogClass.AudioRenderer, $"Dropping voice {voice.NodeId}"); + + for (; i < commandBuffer.CommandList.Commands.Count; i++) + { + ICommand command = commandBuffer.CommandList.Commands[i]; + + if (command.NodeId != targetNodeId) + { + break; + } + + if (command.CommandType == CommandType.DepopPrepare) + { + command.Enabled = true; + } + else if (command.CommandType == CommandType.Performance || !command.Enabled) + { + continue; + } + else + { + command.Enabled = false; + + voicesEstimatedTime -= (uint)(_voiceDropParameter * command.EstimatedProcessingTime); + } + } + } + + return voiceDropped; + } + + private void GenerateCommandList(out CommandList commandList) + { + Debug.Assert(_executionMode == AudioRendererExecutionMode.Auto); + + PoolMapper.ClearUsageState(_memoryPools); + + ulong startTicks = GetSystemTicks(); + + commandList = new CommandList(this); + + if (_performanceManager != null) + { + _performanceManager.TapFrame(_isDspRunningBehind, _voiceDropCount, _renderingStartTick); + + _isDspRunningBehind = false; + _voiceDropCount = 0; + _renderingStartTick = 0; + } + + CommandBuffer commandBuffer = new CommandBuffer(commandList, _commandProcessingTimeEstimator); + + CommandGenerator commandGenerator = new CommandGenerator(commandBuffer, GetContext(), _voiceContext, _mixContext, _effectContext, _sinkContext, _splitterContext, _performanceManager); + + _voiceContext.Sort(); + commandGenerator.GenerateVoices(); + + uint voicesEstimatedTime = (uint)(_voiceDropParameter * commandBuffer.EstimatedProcessingTime); + + commandGenerator.GenerateSubMixes(); + commandGenerator.GenerateFinalMixes(); + commandGenerator.GenerateSinks(); + + uint totalEstimatedTime = (uint)(_voiceDropParameter * commandBuffer.EstimatedProcessingTime); + + if (_voiceDropEnabled) + { + long maxDspTime = GetMaxAllocatedTimeForDsp(); + + long restEstimateTime = totalEstimatedTime - voicesEstimatedTime; + + long deltaTimeDsp = Math.Max(maxDspTime - restEstimateTime, 0); + + _voiceDropCount = ComputeVoiceDrop(commandBuffer, voicesEstimatedTime, deltaTimeDsp); + } + + _voiceContext.UpdateForCommandGeneration(); + + if (_behaviourContext.IsEffectInfoVersion2Supported()) + { + _effectContext.UpdateResultStateForCommandGeneration(); + } + + ulong endTicks = GetSystemTicks(); + + _totalElapsedTicks = endTicks - startTicks; + + _renderingStartTick = GetSystemTicks(); + _elapsedFrameCount++; + } + + private int GetMaxAllocatedTimeForDsp() + { + return (int)(Constants.AudioProcessorMaxUpdateTimePerSessions * _behaviourContext.GetAudioRendererProcessingTimeLimit() * (GetRenderingTimeLimit() / 100.0f)); + } + + public void SendCommands() + { + lock (_lock) + { + if (_isActive) + { + _terminationEvent.Reset(); + + if (!_manager.Processor.HasRemainingCommands(_sessionId)) + { + GenerateCommandList(out CommandList commands); + + _manager.Processor.Send(_sessionId, + commands, + GetMaxAllocatedTimeForDsp(), + _appletResourceId); + + _systemEvent.Signal(); + } + else + { + _isDspRunningBehind = true; + } + } + else + { + _terminationEvent.Set(); + } + } + } + + public uint GetMixBufferCount() + { + return _mixBufferCount; + } + + public void SetRenderingTimeLimitPercent(uint percent) + { + Debug.Assert(percent <= 100); + + _renderingTimeLimitPercent = percent; + } + + public uint GetRenderingTimeLimit() + { + return _renderingTimeLimitPercent; + } + + public Memory GetMixBuffer() + { + return _mixBuffer; + } + + public uint GetSampleCount() + { + return _sampleCount; + } + + public uint GetSampleRate() + { + return _sampleRate; + } + + public uint GetVoiceChannelCountMax() + { + return _voiceChannelCountMax; + } + + public bool IsActive() + { + return _isActive; + } + + private RendererSystemContext GetContext() + { + return new RendererSystemContext + { + ChannelCount = _manager.Processor.OutputDevices[_sessionId].GetChannelCount(), + BehaviourContext = _behaviourContext, + DepopBuffer = _depopBuffer, + MixBufferCount = GetMixBufferCount(), + SessionId = _sessionId, + UpsamplerManager = _upsamplerManager + }; + } + + public int GetSessionId() + { + return _sessionId; + } + + public static ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter) + { + BehaviourContext behaviourContext = new BehaviourContext(); + + behaviourContext.SetUserRevision(parameter.Revision); + + uint mixesCount = parameter.SubMixBufferCount + 1; + + uint memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * Constants.VoiceWaveBufferCount; + + ulong size = 0; + + // Mix Buffers + size = WorkBufferAllocator.GetTargetSize(size, parameter.SampleCount * (Constants.VoiceChannelCountMax + parameter.MixBufferCount), 0x10); + + // Upsampler workbuffer + size = WorkBufferAllocator.GetTargetSize(size, Constants.TargetSampleCount * (Constants.VoiceChannelCountMax + parameter.MixBufferCount) * (parameter.SinkCount + parameter.SubMixBufferCount), 0x10); + + // Depop buffer + size = WorkBufferAllocator.GetTargetSize(size, BitUtils.AlignUp(parameter.MixBufferCount, Constants.BufferAlignment), Constants.BufferAlignment); + + // Voice + size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, VoiceState.Alignment); + size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, 0x10); + size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, VoiceChannelResource.Alignment); + size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, VoiceUpdateState.Align); + + // Mix + size = WorkBufferAllocator.GetTargetSize(size, mixesCount, MixState.Alignment); + size = WorkBufferAllocator.GetTargetSize(size, parameter.EffectCount * mixesCount, 0x10); + size = WorkBufferAllocator.GetTargetSize(size, mixesCount, 0x10); + + if (behaviourContext.IsSplitterSupported()) + { + size += (ulong)BitUtils.AlignUp(NodeStates.GetWorkBufferSize((int)mixesCount) + EdgeMatrix.GetWorkBufferSize((int)mixesCount), 0x10); + } + + // Memory Pool + size = WorkBufferAllocator.GetTargetSize(size, memoryPoolCount, MemoryPoolState.Alignment); + + // Splitter + size = SplitterContext.GetWorkBufferSize(size, ref behaviourContext, ref parameter); + + // DSP Voice + size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, VoiceUpdateState.Align); + + // Performance + if (parameter.PerformanceMetricFramesCount > 0) + { + ulong performanceMetricsPerFramesSize = PerformanceManager.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter, ref behaviourContext) * (parameter.PerformanceMetricFramesCount + 1) + 0xC; + + size += BitUtils.AlignUp(performanceMetricsPerFramesSize, Constants.PerformanceMetricsPerFramesSizeAlignment); + } + + return BitUtils.AlignUp(size, Constants.WorkBufferAlignment); + } + + public ResultCode QuerySystemEvent(out IWritableEvent systemEvent) + { + systemEvent = default; + + if (_executionMode == AudioRendererExecutionMode.Manual) + { + return ResultCode.UnsupportedOperation; + } + + systemEvent = _systemEvent; + + return ResultCode.Success; + } + + public void Dispose() + { + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (_isActive) + { + Stop(); + } + + PoolMapper mapper = new PoolMapper(_processHandle, false); + mapper.Unmap(ref _dspMemoryPoolState); + + PoolMapper.ClearUsageState(_memoryPools); + + for (int i = 0; i < _memoryPoolCount; i++) + { + ref MemoryPoolState memoryPool = ref _memoryPools.Span[i]; + + if (memoryPool.IsMapped()) + { + mapper.Unmap(ref memoryPool); + } + } + + _manager.Unregister(this); + _terminationEvent.Dispose(); + _workBufferMemoryPin.Dispose(); + + if (MemoryManager is IRefCounted rc) + { + rc.DecrementReferenceCount(); + + MemoryManager = null; + } + } + } + + public void SetVoiceDropParameter(float voiceDropParameter) + { + _voiceDropParameter = Math.Clamp(voiceDropParameter, 0.0f, 2.0f); + } + + public float GetVoiceDropParameter() + { + return _voiceDropParameter; + } + + public ResultCode ExecuteAudioRendererRendering() + { + if (_executionMode == AudioRendererExecutionMode.Manual && _renderingDevice == AudioRendererRenderingDevice.Cpu) + { + // NOTE: Here Nintendo aborts with this error code, we don't want that. + return ResultCode.InvalidExecutionContextOperation; + } + + return ResultCode.UnsupportedOperation; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs b/src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs new file mode 100644 index 00000000..4de0ad16 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs @@ -0,0 +1,405 @@ +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Renderer.Dsp; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.Memory; +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// The audio renderer manager. + /// + public class AudioRendererManager : IDisposable + { + /// + /// Lock used for session allocation. + /// + private object _sessionLock = new object(); + + /// + /// Lock used to control the running state. + /// + private object _audioProcessorLock = new object(); + + /// + /// The session ids allocation table. + /// + private int[] _sessionIds; + + /// + /// The events linked to each session. + /// + private IWritableEvent[] _sessionsSystemEvent; + + /// + /// The sessions instances. + /// + private AudioRenderSystem[] _sessions; + + /// + /// The count of active sessions. + /// + private int _activeSessionCount; + + /// + /// The worker thread used to run . + /// + private Thread _workerThread; + + /// + /// Indicate if the worker thread and are running. + /// + private bool _isRunning; + + /// + /// The audio device driver to create audio outputs. + /// + private IHardwareDeviceDriver _deviceDriver; + + /// + /// Tick source used to measure elapsed time. + /// + public ITickSource TickSource { get; } + + /// + /// The instance associated to this manager. + /// + public AudioProcessor Processor { get; } + + /// + /// The dispose state. + /// + private int _disposeState; + + /// + /// Create a new . + /// + /// Tick source used to measure elapsed time. + public AudioRendererManager(ITickSource tickSource) + { + Processor = new AudioProcessor(); + TickSource = tickSource; + _sessionIds = new int[Constants.AudioRendererSessionCountMax]; + _sessions = new AudioRenderSystem[Constants.AudioRendererSessionCountMax]; + _activeSessionCount = 0; + + for (int i = 0; i < _sessionIds.Length; i++) + { + _sessionIds[i] = i; + } + } + + /// + /// Initialize the . + /// + /// The events associated to each session. + /// The device driver to use to create audio outputs. + public void Initialize(IWritableEvent[] sessionSystemEvents, IHardwareDeviceDriver deviceDriver) + { + _sessionsSystemEvent = sessionSystemEvents; + _deviceDriver = deviceDriver; + } + + /// + /// Get the work buffer size required by a session. + /// + /// The user configuration + /// The work buffer size required by a session. + public static ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter) + { + return AudioRenderSystem.GetWorkBufferSize(ref parameter); + } + + /// + /// Acquire a new session id. + /// + /// A new session id. + private int AcquireSessionId() + { + lock (_sessionLock) + { + int index = _activeSessionCount; + + Debug.Assert(index < _sessionIds.Length); + + int sessionId = _sessionIds[index]; + + _sessionIds[index] = -1; + + _activeSessionCount++; + + Logger.Info?.Print(LogClass.AudioRenderer, $"Registered new renderer ({sessionId})"); + + return sessionId; + } + } + + /// + /// Release a given . + /// + /// The session id to release. + private void ReleaseSessionId(int sessionId) + { + lock (_sessionLock) + { + Debug.Assert(_activeSessionCount > 0); + + int newIndex = --_activeSessionCount; + + _sessionIds[newIndex] = sessionId; + } + + Logger.Info?.Print(LogClass.AudioRenderer, $"Unregistered renderer ({sessionId})"); + } + + /// + /// Check if there is any audio renderer active. + /// + /// Returns true if there is any audio renderer active. + private bool HasAnyActiveRendererLocked() + { + foreach (AudioRenderSystem renderer in _sessions) + { + if (renderer != null) + { + return true; + } + } + + return false; + } + + /// + /// Start the and worker thread. + /// + private void StartLocked(float volume) + { + _isRunning = true; + + // TODO: virtual device mapping (IAudioDevice) + Processor.Start(_deviceDriver, volume); + + _workerThread = new Thread(SendCommands) + { + Name = "AudioRendererManager.Worker" + }; + + _workerThread.Start(); + } + + /// + /// Stop the and worker thread. + /// + private void StopLocked() + { + _isRunning = false; + + _workerThread.Join(); + Processor.Stop(); + + Logger.Info?.Print(LogClass.AudioRenderer, "Stopped audio renderer"); + } + + /// + /// Stop sending commands to the without stopping the worker thread. + /// + public void StopSendingCommands() + { + lock (_sessionLock) + { + foreach (AudioRenderSystem renderer in _sessions) + { + renderer?.Disable(); + } + } + + lock (_audioProcessorLock) + { + if (_isRunning) + { + StopLocked(); + } + } + } + + /// + /// Worker main function. This is used to dispatch audio renderer commands to the . + /// + private void SendCommands() + { + Logger.Info?.Print(LogClass.AudioRenderer, "Starting audio renderer"); + Processor.Wait(); + + while (_isRunning) + { + lock (_sessionLock) + { + foreach (AudioRenderSystem renderer in _sessions) + { + renderer?.SendCommands(); + } + } + + Processor.Signal(); + Processor.Wait(); + } + } + + /// + /// Register a new . + /// + /// The to register. + private void Register(AudioRenderSystem renderer, float volume) + { + lock (_sessionLock) + { + _sessions[renderer.GetSessionId()] = renderer; + } + + lock (_audioProcessorLock) + { + if (!_isRunning) + { + StartLocked(volume); + } + } + } + + /// + /// Unregister a new . + /// + /// The to unregister. + internal void Unregister(AudioRenderSystem renderer) + { + lock (_sessionLock) + { + int sessionId = renderer.GetSessionId(); + + _sessions[renderer.GetSessionId()] = null; + + ReleaseSessionId(sessionId); + } + + lock (_audioProcessorLock) + { + if (_isRunning && !HasAnyActiveRendererLocked()) + { + StopLocked(); + } + } + } + + /// + /// Open a new + /// + /// The new + /// The memory manager that will be used for all guest memory operations. + /// The user configuration + /// The applet resource user id of the application. + /// The guest work buffer address. + /// The guest work buffer size. + /// The process handle of the application. + /// A reporting an error or a success. + public ResultCode OpenAudioRenderer( + out AudioRenderSystem renderer, + IVirtualMemoryManager memoryManager, + ref AudioRendererConfiguration parameter, + ulong appletResourceUserId, + ulong workBufferAddress, + ulong workBufferSize, + uint processHandle, + float volume) + { + int sessionId = AcquireSessionId(); + + AudioRenderSystem audioRenderer = new AudioRenderSystem(this, _sessionsSystemEvent[sessionId]); + + // TODO: Eventually, we should try to use the guest supplied work buffer instead of allocating + // our own. However, it was causing problems on some applications that would unmap the memory + // before the audio renderer was fully disposed. + Memory workBufferMemory = GC.AllocateArray((int)workBufferSize, pinned: true); + + ResultCode result = audioRenderer.Initialize( + ref parameter, + processHandle, + workBufferMemory, + workBufferAddress, + workBufferSize, + sessionId, + appletResourceUserId, + memoryManager); + + if (result == ResultCode.Success) + { + renderer = audioRenderer; + + Register(renderer, volume); + } + else + { + ReleaseSessionId(sessionId); + + renderer = null; + } + + return result; + } + + public float GetVolume() + { + if (Processor != null) + { + return Processor.GetVolume(); + } + + return 0f; + } + + public void SetVolume(float volume) + { + Processor?.SetVolume(volume); + } + + public void Dispose() + { + if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) + { + Dispose(true); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Clone the sessions array to dispose them outside the lock. + AudioRenderSystem[] sessions; + + lock (_sessionLock) + { + sessions = _sessions.ToArray(); + } + + foreach (AudioRenderSystem renderer in sessions) + { + renderer?.Dispose(); + } + + lock (_audioProcessorLock) + { + if (_isRunning) + { + StopLocked(); + } + } + + Processor.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs new file mode 100644 index 00000000..821947a9 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs @@ -0,0 +1,453 @@ +using System; +using System.Diagnostics; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// Behaviour context. + /// + /// This handles features based on the audio renderer revision provided by the user. + public class BehaviourContext + { + /// + /// The base magic of the Audio Renderer revision. + /// + public const int BaseRevisionMagic = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('0' << 24); + + /// + /// REV1: first revision. + /// + public const int Revision1 = 1 << 24; + + /// + /// REV2: Added support for splitter and fix GC-ADPCM context not being provided to the DSP. + /// + /// This was added in system update 2.0.0 + public const int Revision2 = 2 << 24; + + /// + /// REV3: Incremented the max pre-delay from 150 to 350 for the reverb command and removed the (unused) codec system. + /// + /// This was added in system update 3.0.0 + public const int Revision3 = 3 << 24; + + /// + /// REV4: Added USB audio device support and incremented the rendering limit percent to 75%. + /// + /// This was added in system update 4.0.0 + public const int Revision4 = 4 << 24; + + /// + /// REV5: , were added to voice. + /// A new performance frame format (version 2) was added with support for more information about DSP timing. + /// was added to supply the count of update done sent to the DSP. + /// A new version of the command estimator was added to address timing changes caused by the voice changes. + /// Additionally, the rendering limit percent was incremented to 80%. + /// + /// + /// This was added in system update 6.0.0 + public const int Revision5 = 5 << 24; + + /// + /// REV6: This fixed a bug in the biquad filter command not clearing up with usage state. + /// + /// This was added in system update 6.1.0 + public const int Revision6 = 6 << 24; + + /// + /// REV7: Client side (finally) doesn't send all the mix client state to the server and can do partial updates. + /// + /// This was added in system update 8.0.0 + public const int Revision7 = 7 << 24; + + /// + /// REV8: + /// Wavebuffer was changed to support more control over loop (you can now specify where to start and end a loop, and how many times to loop). + /// was added (see for more info). + /// Final leftovers of the codec system were removed. + /// support was added. + /// A new version of the command estimator was added to address timing changes caused by the voice and command changes. + /// + /// This was added in system update 9.0.0 + public const int Revision8 = 8 << 24; + + /// + /// REV9: + /// EffectInfo parameters were revisited with a new revision (version 2) allowing more data control between the client and server. + /// A new effect was added: Limiter. This effect is effectively implemented with a DRC while providing statistics on the processing on . + /// + /// This was added in system update 12.0.0 + public const int Revision9 = 9 << 24; + + /// + /// REV10: + /// Added Bluetooth audio device support and removed the unused "GetAudioSystemMasterVolumeSetting" audio device API. + /// A new effect was added: Capture. This effect allows the client side to capture audio buffers of a mix. + /// A new command was added for double biquad filters on voices. This is implemented using a direct form 1 (instead of the usual direct form 2). + /// A new version of the command estimator was added to support the new commands. + /// + /// This was added in system update 13.0.0 + public const int Revision10 = 10 << 24; + + /// + /// REV11: + /// The "legacy" effects (Delay, Reverb and Reverb 3D) were updated to match the standard channel mapping used by the audio renderer. + /// A new effect was added: Compressor. This effect is effectively implemented with a DRC. + /// A new version of the command estimator was added to address timing changes caused by the legacy effects changes. + /// A voice drop parameter was added in 15.0.0: This allows an application to amplify or attenuate the estimated time of DSP commands. + /// + /// This was added in system update 14.0.0 but some changes were made in 15.0.0 + public const int Revision11 = 11 << 24; + + /// + /// Last revision supported by the implementation. + /// + public const int LastRevision = Revision11; + + /// + /// Target revision magic supported by the implementation. + /// + public const int ProcessRevision = BaseRevisionMagic + LastRevision; + + /// + /// Get the revision number from the revision magic. + /// + /// The revision magic. + /// The revision number. + public static int GetRevisionNumber(int revision) => (revision - BaseRevisionMagic) >> 24; + + /// + /// Current active revision. + /// + public int UserRevision { get; private set; } + + /// + /// Error storage. + /// + private ErrorInfo[] _errorInfos; + + /// + /// Current position in the array. + /// + private uint _errorIndex; + + /// + /// Current flags of the . + /// + private ulong _flags; + + /// + /// Create a new instance of . + /// + public BehaviourContext() + { + UserRevision = 0; + _errorInfos = new ErrorInfo[Constants.MaxErrorInfos]; + _errorIndex = 0; + } + + /// + /// Set the active revision. + /// + /// The active revision. + public void SetUserRevision(int userRevision) + { + UserRevision = userRevision; + } + + /// + /// Update flags of the . + /// + /// The new flags. + public void UpdateFlags(ulong flags) + { + _flags = flags; + } + + /// + /// Check if a given revision is valid/supported. + /// + /// The revision magic to check. + /// Returns true if the given revision is valid/supported + public static bool CheckValidRevision(int revision) + { + return GetRevisionNumber(revision) <= GetRevisionNumber(ProcessRevision); + } + + /// + /// Check if the given revision is greater than or equal the supported revision. + /// + /// The revision magic to check. + /// The revision magic of the supported revision. + /// Returns true if the given revision is greater than or equal the supported revision. + public static bool CheckFeatureSupported(int revision, int supportedRevision) + { + int revA = GetRevisionNumber(revision); + int revB = GetRevisionNumber(supportedRevision); + + if (revA > LastRevision) + { + revA = 1; + } + + if (revB > LastRevision) + { + revB = 1; + } + + return revA >= revB; + } + + /// + /// Check if the memory pool mapping bypass flag is active. + /// + /// True if the memory pool mapping bypass flag is active. + public bool IsMemoryPoolForceMappingEnabled() + { + return (_flags & 1) != 0; + } + + /// + /// Check if the audio renderer should fix the GC-ADPCM context not being provided to the DSP. + /// + /// True if if the audio renderer should fix it. + public bool IsAdpcmLoopContextBugFixed() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision2); + } + + /// + /// Check if the audio renderer should accept splitters. + /// + /// True if the audio renderer should accept splitters. + public bool IsSplitterSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision2); + } + + /// + /// Check if the audio renderer should use a max pre-delay of 350 instead of 150. + /// + /// True if the max pre-delay must be 350. + public bool IsLongSizePreDelaySupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision3); + } + + /// + /// Check if the audio renderer should expose USB audio device. + /// + /// True if the audio renderer should expose USB audio device. + public bool IsAudioUsbDeviceOutputSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision4); + } + + /// + /// Get the percentage allocated to the audio renderer on the DSP for processing. + /// + /// The percentage allocated to the audio renderer on the DSP for processing. + public float GetAudioRendererProcessingTimeLimit() + { + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5)) + { + return 0.80f; + } + else if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision4)) + { + return 0.75f; + } + + return 0.70f; + } + + /// + /// Check if the audio render should support voice flushing. + /// + /// True if the audio render should support voice flushing. + public bool IsFlushVoiceWaveBuffersSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5); + } + + /// + /// Check if the audio renderer should trust the user destination count in . + /// + /// True if the audio renderer should trust the user destination count. + public bool IsSplitterBugFixed() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5); + } + + /// + /// Check if the audio renderer should supply the elapsed frame count to the user when updating. + /// + /// True if the audio renderer should supply the elapsed frame count to the user when updating. + public bool IsElapsedFrameCountSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5); + } + + /// + /// Get the performance metric data format version. + /// + /// The performance metric data format version. + public uint GetPerformanceMetricsDataFormat() + { + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5)) + { + return 2; + } + else + { + return 1; + } + } + + /// + /// Check if the audio renderer should support . + /// + /// True if the audio renderer should support . + public bool IsDecodingBehaviourFlagSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5); + } + + /// + /// Check if the audio renderer should fix the biquad filter command not clearing up with usage state. + /// + /// True if the biquad filter state should be cleared. + public bool IsBiquadFilterEffectStateClearBugFixed() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision6); + } + + /// + /// Check if the audio renderer should accept partial mix updates. + /// + /// True if the audio renderer should accept partial mix updates. + public bool IsMixInParameterDirtyOnlyUpdateSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision7); + } + + /// + /// Check if the audio renderer should use the new wavebuffer format. + /// + /// True if the audio renderer should use the new wavebuffer format. + public bool IsWaveBufferVersion2Supported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision8); + } + + /// + /// Check if the audio renderer should use the new effect info format. + /// + /// True if the audio renderer should use the new effect info format. + public bool IsEffectInfoVersion2Supported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision9); + } + + /// + /// Check if the audio renderer should use an optimized Biquad Filter (Direct Form 1) in case of two biquad filters are defined on a voice. + /// + /// True if the audio renderer should use the optimization. + public bool IsBiquadFilterGroupedOptimizationSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10); + } + + /// + /// Check if the audio renderer should support new channel resource mapping for 5.1 on Delay, Reverb and Reverb 3D effects. + /// + /// True if the audio renderer support new channel resource mapping for 5.1. + public bool IsNewEffectChannelMappingSupported() + { + return CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11); + } + + /// + /// Get the version of the . + /// + /// The version of the . + public int GetCommandProcessingTimeEstimatorVersion() + { + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision11)) + { + return 5; + } + + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision10)) + { + return 4; + } + + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision8)) + { + return 3; + } + + if (CheckFeatureSupported(UserRevision, BaseRevisionMagic + Revision5)) + { + return 2; + } + + return 1; + } + + /// + /// Append a new to the error array. + /// + /// The new to add. + public void AppendError(ref ErrorInfo errorInfo) + { + Debug.Assert(errorInfo.ErrorCode == ResultCode.Success); + + if (_errorIndex <= Constants.MaxErrorInfos - 1) + { + _errorInfos[_errorIndex++] = errorInfo; + } + } + + /// + /// Copy the internal array to the given and output the count copied. + /// + /// The output . + /// The output error count containing the count of copied. + public void CopyErrorInfo(Span errorInfos, out uint errorCount) + { + if (errorInfos.Length != Constants.MaxErrorInfos) + { + throw new ArgumentException("Invalid size of errorInfos span!"); + } + + errorCount = Math.Min(_errorIndex, Constants.MaxErrorInfos); + + for (int i = 0; i < Constants.MaxErrorInfos; i++) + { + if (i < errorCount) + { + errorInfos[i] = _errorInfos[i]; + } + else + { + errorInfos[i] = new ErrorInfo + { + ErrorCode = 0, + ExtraErrorInfo = 0 + }; + } + } + } + + /// + /// Clear the array. + /// + public void ClearError() + { + _errorIndex = 0; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs b/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs new file mode 100644 index 00000000..905cb205 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs @@ -0,0 +1,568 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.Command; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.Performance; +using Ryujinx.Audio.Renderer.Server.Sink; +using Ryujinx.Audio.Renderer.Server.Upsampler; +using Ryujinx.Audio.Renderer.Server.Voice; +using System; +using CpuAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// An API to generate commands and aggregate them into a . + /// + public class CommandBuffer + { + /// + /// The command processing time estimator in use. + /// + private ICommandProcessingTimeEstimator _commandProcessingTimeEstimator; + + /// + /// The estimated total processing time. + /// + public uint EstimatedProcessingTime { get; set; } + + /// + /// The command list that is populated by the . + /// + public CommandList CommandList { get; } + + /// + /// Create a new . + /// + /// The command list that will store the generated commands. + /// The command processing time estimator to use. + public CommandBuffer(CommandList commandList, ICommandProcessingTimeEstimator commandProcessingTimeEstimator) + { + CommandList = commandList; + EstimatedProcessingTime = 0; + _commandProcessingTimeEstimator = commandProcessingTimeEstimator; + } + + /// + /// Add a new generated command to the . + /// + /// The command to add. + private void AddCommand(ICommand command) + { + EstimatedProcessingTime += command.EstimatedProcessingTime; + + CommandList.AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The node id associated to this command. + public void GenerateClearMixBuffer(int nodeId) + { + ClearMixBufferCommand command = new ClearMixBufferCommand(nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The voice state associated. + /// The depop buffer. + /// The buffer count. + /// The target buffer offset. + /// The node id associated to this command. + /// Set to true if the voice was playing previously. + public void GenerateDepopPrepare(Memory state, Memory depopBuffer, uint bufferCount, uint bufferOffset, int nodeId, bool wasPlaying) + { + DepopPrepareCommand command = new DepopPrepareCommand(state, depopBuffer, bufferCount, bufferOffset, nodeId, wasPlaying); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The . + /// The performance operation to perform. + /// The node id associated to this command. + public void GeneratePerformance(ref PerformanceEntryAddresses performanceEntryAddresses, PerformanceCommand.Type type, int nodeId) + { + PerformanceCommand command = new PerformanceCommand(ref performanceEntryAddresses, type, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The previous volume. + /// The new volume. + /// The index of the mix buffer to use. + /// The node id associated to this command. + public void GenerateVolumeRamp(float previousVolume, float volume, uint bufferIndex, int nodeId) + { + VolumeRampCommand command = new VolumeRampCommand(previousVolume, volume, bufferIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The to generate the command from. + /// The to generate the command from. + /// The output buffer index to use. + /// The target channel index. + /// The node id associated to this command. + public void GenerateDataSourceVersion2(ref VoiceState voiceState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + DataSourceVersion2Command command = new DataSourceVersion2Command(ref voiceState, state, outputBufferIndex, channelIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The to generate the command from. + /// The to generate the command from. + /// The output buffer index to use. + /// The target channel index. + /// The node id associated to this command. + public void GeneratePcmInt16DataSourceVersion1(ref VoiceState voiceState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + PcmInt16DataSourceCommandVersion1 command = new PcmInt16DataSourceCommandVersion1(ref voiceState, state, outputBufferIndex, channelIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The to generate the command from. + /// The to generate the command from. + /// The output buffer index to use. + /// The target channel index. + /// The node id associated to this command. + public void GeneratePcmFloatDataSourceVersion1(ref VoiceState voiceState, Memory state, ushort outputBufferIndex, ushort channelIndex, int nodeId) + { + PcmFloatDataSourceCommandVersion1 command = new PcmFloatDataSourceCommandVersion1(ref voiceState, state, outputBufferIndex, channelIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The to generate the command from. + /// The to generate the command from. + /// The output buffer index to use. + /// The node id associated to this command. + public void GenerateAdpcmDataSourceVersion1(ref VoiceState voiceState, Memory state, ushort outputBufferIndex, int nodeId) + { + AdpcmDataSourceCommandVersion1 command = new AdpcmDataSourceCommandVersion1(ref voiceState, state, outputBufferIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The base index of the input and output buffer. + /// The biquad filter parameter. + /// The biquad state. + /// The input buffer offset. + /// The output buffer offset. + /// Set to true if the biquad filter state needs to be initialized. + /// The node id associated to this command. + public void GenerateBiquadFilter(int baseIndex, ref BiquadFilterParameter filter, Memory biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, bool needInitialization, int nodeId) + { + BiquadFilterCommand command = new BiquadFilterCommand(baseIndex, ref filter, biquadFilterStateMemory, inputBufferOffset, outputBufferOffset, needInitialization, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The base index of the input and output buffer. + /// The biquad filter parameters. + /// The biquad states. + /// The input buffer offset. + /// The output buffer offset. + /// Set to true if the biquad filter state is initialized. + /// The node id associated to this command. + public void GenerateGroupedBiquadFilter(int baseIndex, ReadOnlySpan filters, Memory biquadFilterStatesMemory, int inputBufferOffset, int outputBufferOffset, ReadOnlySpan isInitialized, int nodeId) + { + GroupedBiquadFilterCommand command = new GroupedBiquadFilterCommand(baseIndex, filters, biquadFilterStatesMemory, inputBufferOffset, outputBufferOffset, isInitialized, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The mix buffer count. + /// The base input index. + /// The base output index. + /// The previous volume. + /// The new volume. + /// The to generate the command from. + /// The node id associated to this command. + public void GenerateMixRampGrouped(uint mixBufferCount, uint inputBufferIndex, uint outputBufferIndex, Span previousVolume, Span volume, Memory state, int nodeId) + { + MixRampGroupedCommand command = new MixRampGroupedCommand(mixBufferCount, inputBufferIndex, outputBufferIndex, previousVolume, volume, state, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The previous volume. + /// The new volume. + /// The input buffer index. + /// The output buffer index. + /// The index in the array to store the ramped sample. + /// The to generate the command from. + /// The node id associated to this command. + public void GenerateMixRamp(float previousVolume, float volume, uint inputBufferIndex, uint outputBufferIndex, int lastSampleIndex, Memory state, int nodeId) + { + MixRampCommand command = new MixRampCommand(previousVolume, volume, inputBufferIndex, outputBufferIndex, lastSampleIndex, state, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The depop buffer. + /// The target buffer offset. + /// The buffer count. + /// The node id associated to this command. + /// The target sample rate in use. + public void GenerateDepopForMixBuffersCommand(Memory depopBuffer, uint bufferOffset, uint bufferCount, int nodeId, uint sampleRate) + { + DepopForMixBuffersCommand command = new DepopForMixBuffersCommand(depopBuffer, bufferOffset, bufferCount, nodeId, sampleRate); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The input buffer index. + /// The output buffer index. + /// The node id associated to this command. + public void GenerateCopyMixBuffer(uint inputBufferIndex, uint outputBufferIndex, int nodeId) + { + CopyMixBufferCommand command = new CopyMixBufferCommand(inputBufferIndex, outputBufferIndex, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The input buffer index. + /// The output buffer index. + /// The node id associated to this command. + /// The mix volume. + public void GenerateMix(uint inputBufferIndex, uint outputBufferIndex, int nodeId, float volume) + { + MixCommand command = new MixCommand(inputBufferIndex, outputBufferIndex, nodeId, volume); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The reverb parameter. + /// The reverb state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + /// If set to true, the long size pre-delay is supported. + /// If set to true, the new effect channel mapping for 5.1 is supported. + public void GenerateReverbEffect(uint bufferOffset, ReverbParameter parameter, Memory state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool isLongSizePreDelaySupported, bool newEffectChannelMappingSupported) + { + if (parameter.IsChannelCountValid()) + { + ReverbCommand command = new ReverbCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, isLongSizePreDelaySupported, newEffectChannelMappingSupported); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The reverb 3d parameter. + /// The reverb 3d state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + /// If set to true, the new effect channel mapping for 5.1 is supported. + public void GenerateReverb3dEffect(uint bufferOffset, Reverb3dParameter parameter, Memory state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool newEffectChannelMappingSupported) + { + if (parameter.IsChannelCountValid()) + { + Reverb3dCommand command = new Reverb3dCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, newEffectChannelMappingSupported); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The delay parameter. + /// The delay state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + /// If set to true, the new effect channel mapping for 5.1 is supported. + public void GenerateDelayEffect(uint bufferOffset, DelayParameter parameter, Memory state, bool isEnabled, CpuAddress workBuffer, int nodeId, bool newEffectChannelMappingSupported) + { + if (parameter.IsChannelCountValid()) + { + DelayCommand command = new DelayCommand(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId, newEffectChannelMappingSupported); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The limiter parameter. + /// The limiter state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + public void GenerateLimiterEffectVersion1(uint bufferOffset, LimiterParameter parameter, Memory state, bool isEnabled, ulong workBuffer, int nodeId) + { + if (parameter.IsChannelCountValid()) + { + LimiterCommandVersion1 command = new LimiterCommandVersion1(bufferOffset, parameter, state, isEnabled, workBuffer, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The limiter parameter. + /// The limiter state. + /// The DSP effect result state. + /// Set to true if the effect should be active. + /// The work buffer to use for processing. + /// The node id associated to this command. + public void GenerateLimiterEffectVersion2(uint bufferOffset, LimiterParameter parameter, Memory state, Memory effectResultState, bool isEnabled, ulong workBuffer, int nodeId) + { + if (parameter.IsChannelCountValid()) + { + LimiterCommandVersion2 command = new LimiterCommandVersion2(bufferOffset, parameter, state, effectResultState, isEnabled, workBuffer, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The input buffer offset. + /// The output buffer offset. + /// The aux state. + /// Set to true if the effect should be active. + /// The limit of the circular buffer. + /// The guest address of the output buffer. + /// The guest address of the input buffer. + /// The count to add on the offset after write/read operations. + /// The write offset. + /// The node id associated to this command. + public void GenerateAuxEffect(uint bufferOffset, byte inputBufferOffset, byte outputBufferOffset, ref AuxiliaryBufferAddresses state, bool isEnabled, uint countMax, CpuAddress outputBuffer, CpuAddress inputBuffer, uint updateCount, uint writeOffset, int nodeId) + { + if (state.SendBufferInfoBase != 0 && state.ReturnBufferInfoBase != 0) + { + AuxiliaryBufferCommand command = new AuxiliaryBufferCommand(bufferOffset, inputBufferOffset, outputBufferOffset, ref state, isEnabled, countMax, outputBuffer, inputBuffer, updateCount, writeOffset, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target buffer offset. + /// The input buffer offset. + /// The capture state. + /// Set to true if the effect should be active. + /// The limit of the circular buffer. + /// The guest address of the output buffer. + /// The count to add on the offset after write operations. + /// The write offset. + /// The node id associated to this command. + public void GenerateCaptureEffect(uint bufferOffset, byte inputBufferOffset, ulong sendBufferInfo, bool isEnabled, uint countMax, CpuAddress outputBuffer, uint updateCount, uint writeOffset, int nodeId) + { + if (sendBufferInfo != 0) + { + CaptureBufferCommand command = new CaptureBufferCommand(bufferOffset, inputBufferOffset, sendBufferInfo, isEnabled, countMax, outputBuffer, updateCount, writeOffset, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + public void GenerateCompressorEffect(uint bufferOffset, CompressorParameter parameter, Memory state, bool isEnabled, int nodeId) + { + if (parameter.IsChannelCountValid()) + { + CompressorCommand command = new CompressorCommand(bufferOffset, parameter, state, isEnabled, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } + + /// + /// Generate a new . + /// + /// The target volume to apply. + /// The offset of the mix buffer. + /// The node id associated to this command. + public void GenerateVolume(float volume, uint bufferOffset, int nodeId) + { + VolumeCommand command = new VolumeCommand(volume, bufferOffset, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The offset of the mix buffer. + /// The of the circular buffer. + /// The node id associated to this command. + public void GenerateCircularBuffer(uint bufferOffset, CircularBufferSink sink, int nodeId) + { + CircularBufferSinkCommand command = new CircularBufferSinkCommand(bufferOffset, ref sink.Parameter, ref sink.CircularBufferAddressInfo, sink.CurrentWriteOffset, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The offset of the mix buffer. + /// The input buffer offset. + /// The output buffer offset. + /// The downmixer parameters to use. + /// The node id associated to this command. + public void GenerateDownMixSurroundToStereo(uint bufferOffset, Span inputBufferOffset, Span outputBufferOffset, float[] downMixParameter, int nodeId) + { + DownMixSurroundToStereoCommand command = new DownMixSurroundToStereoCommand(bufferOffset, inputBufferOffset, outputBufferOffset, downMixParameter, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The offset of the mix buffer. + /// The associated. + /// The total input count. + /// The input buffer mix offset. + /// The buffer count per sample. + /// The source sample count. + /// The source sample rate. + /// The node id associated to this command. + public void GenerateUpsample(uint bufferOffset, UpsamplerState upsampler, uint inputCount, Span inputBufferOffset, uint bufferCountPerSample, uint sampleCount, uint sampleRate, int nodeId) + { + UpsampleCommand command = new UpsampleCommand(bufferOffset, upsampler, inputCount, inputBufferOffset, bufferCountPerSample, sampleCount, sampleRate, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + + /// + /// Create a new . + /// + /// The offset of the mix buffer. + /// The of the device sink. + /// The current audio renderer session id. + /// The mix buffer in use. + /// The node id associated to this command. + public void GenerateDeviceSink(uint bufferOffset, DeviceSink sink, int sessionId, Memory buffer, int nodeId) + { + DeviceSinkCommand command = new DeviceSinkCommand(bufferOffset, sink, sessionId, buffer, nodeId); + + command.EstimatedProcessingTime = _commandProcessingTimeEstimator.Estimate(command); + + AddCommand(command); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs b/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs new file mode 100644 index 00000000..afc1e39b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs @@ -0,0 +1,1028 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.Command; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Audio.Renderer.Server.Mix; +using Ryujinx.Audio.Renderer.Server.Performance; +using Ryujinx.Audio.Renderer.Server.Sink; +using Ryujinx.Audio.Renderer.Server.Splitter; +using Ryujinx.Audio.Renderer.Server.Voice; +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server +{ + public class CommandGenerator + { + private CommandBuffer _commandBuffer; + private RendererSystemContext _rendererContext; + private VoiceContext _voiceContext; + private MixContext _mixContext; + private EffectContext _effectContext; + private SinkContext _sinkContext; + private SplitterContext _splitterContext; + private PerformanceManager _performanceManager; + + public CommandGenerator(CommandBuffer commandBuffer, RendererSystemContext rendererContext, VoiceContext voiceContext, MixContext mixContext, EffectContext effectContext, SinkContext sinkContext, SplitterContext splitterContext, PerformanceManager performanceManager) + { + _commandBuffer = commandBuffer; + _rendererContext = rendererContext; + _voiceContext = voiceContext; + _mixContext = mixContext; + _effectContext = effectContext; + _sinkContext = sinkContext; + _splitterContext = splitterContext; + _performanceManager = performanceManager; + + _commandBuffer.GenerateClearMixBuffer(Constants.InvalidNodeId); + } + + private void GenerateDataSource(ref VoiceState voiceState, Memory dspState, int channelIndex) + { + if (voiceState.MixId != Constants.UnusedMixId) + { + ref MixState mix = ref _mixContext.GetState(voiceState.MixId); + + _commandBuffer.GenerateDepopPrepare(dspState, + _rendererContext.DepopBuffer, + mix.BufferCount, + mix.BufferOffset, + voiceState.NodeId, + voiceState.WasPlaying); + } + else if (voiceState.SplitterId != Constants.UnusedSplitterId) + { + int destinationId = 0; + + while (true) + { + Span destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId++); + + if (destinationSpan.IsEmpty) + { + break; + } + + ref SplitterDestination destination = ref destinationSpan[0]; + + if (destination.IsConfigured()) + { + int mixId = destination.DestinationId; + + if (mixId < _mixContext.GetCount() && mixId != Constants.UnusedSplitterIdInt) + { + ref MixState mix = ref _mixContext.GetState(mixId); + + _commandBuffer.GenerateDepopPrepare(dspState, + _rendererContext.DepopBuffer, + mix.BufferCount, + mix.BufferOffset, + voiceState.NodeId, + voiceState.WasPlaying); + + destination.MarkAsNeedToUpdateInternalState(); + } + } + } + } + + if (!voiceState.WasPlaying) + { + Debug.Assert(voiceState.SampleFormat != SampleFormat.Adpcm || channelIndex == 0); + + if (_rendererContext.BehaviourContext.IsWaveBufferVersion2Supported()) + { + _commandBuffer.GenerateDataSourceVersion2(ref voiceState, + dspState, + (ushort)_rendererContext.MixBufferCount, + (ushort)channelIndex, + voiceState.NodeId); + } + else + { + switch (voiceState.SampleFormat) + { + case SampleFormat.PcmInt16: + _commandBuffer.GeneratePcmInt16DataSourceVersion1(ref voiceState, + dspState, + (ushort)_rendererContext.MixBufferCount, + (ushort)channelIndex, + voiceState.NodeId); + break; + case SampleFormat.PcmFloat: + _commandBuffer.GeneratePcmFloatDataSourceVersion1(ref voiceState, + dspState, + (ushort)_rendererContext.MixBufferCount, + (ushort)channelIndex, + voiceState.NodeId); + break; + case SampleFormat.Adpcm: + _commandBuffer.GenerateAdpcmDataSourceVersion1(ref voiceState, + dspState, + (ushort)_rendererContext.MixBufferCount, + voiceState.NodeId); + break; + default: + throw new NotImplementedException($"Unsupported data source {voiceState.SampleFormat}"); + } + } + } + } + + private void GenerateBiquadFilterForVoice(ref VoiceState voiceState, Memory state, int baseIndex, int bufferOffset, int nodeId) + { + bool supportsOptimizedPath = _rendererContext.BehaviourContext.IsBiquadFilterGroupedOptimizationSupported(); + + if (supportsOptimizedPath && voiceState.BiquadFilters[0].Enable && voiceState.BiquadFilters[1].Enable) + { + Memory biquadStateRawMemory = SpanMemoryManager.Cast(state).Slice(VoiceUpdateState.BiquadStateOffset, VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount); + Memory stateMemory = SpanMemoryManager.Cast(biquadStateRawMemory); + + _commandBuffer.GenerateGroupedBiquadFilter(baseIndex, voiceState.BiquadFilters.AsSpan(), stateMemory, bufferOffset, bufferOffset, voiceState.BiquadFilterNeedInitialization, nodeId); + } + else + { + for (int i = 0; i < voiceState.BiquadFilters.Length; i++) + { + ref BiquadFilterParameter filter = ref voiceState.BiquadFilters[i]; + + if (filter.Enable) + { + Memory biquadStateRawMemory = SpanMemoryManager.Cast(state).Slice(VoiceUpdateState.BiquadStateOffset, VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount); + + Memory stateMemory = SpanMemoryManager.Cast(biquadStateRawMemory); + + _commandBuffer.GenerateBiquadFilter(baseIndex, + ref filter, + stateMemory.Slice(i, 1), + bufferOffset, + bufferOffset, + !voiceState.BiquadFilterNeedInitialization[i], + nodeId); + } + } + } + } + + private void GenerateVoiceMix(Span mixVolumes, Span previousMixVolumes, Memory state, uint bufferOffset, uint bufferCount, uint bufferIndex, int nodeId) + { + if (bufferCount > Constants.VoiceChannelCountMax) + { + _commandBuffer.GenerateMixRampGrouped(bufferCount, + bufferIndex, + bufferOffset, + previousMixVolumes, + mixVolumes, + state, + nodeId); + } + else + { + for (int i = 0; i < bufferCount; i++) + { + float previousMixVolume = previousMixVolumes[i]; + float mixVolume = mixVolumes[i]; + + if (mixVolume != 0.0f || previousMixVolume != 0.0f) + { + _commandBuffer.GenerateMixRamp(previousMixVolume, + mixVolume, + bufferIndex, + bufferOffset + (uint)i, + i, + state, + nodeId); + } + } + } + } + + private void GenerateVoice(ref VoiceState voiceState) + { + int nodeId = voiceState.NodeId; + uint channelsCount = voiceState.ChannelsCount; + + for (int channelIndex = 0; channelIndex < channelsCount; channelIndex++) + { + Memory dspStateMemory = _voiceContext.GetUpdateStateForDsp(voiceState.ChannelResourceIds[channelIndex]); + + ref VoiceChannelResource channelResource = ref _voiceContext.GetChannelResource(voiceState.ChannelResourceIds[channelIndex]); + + PerformanceDetailType dataSourceDetailType = PerformanceDetailType.Adpcm; + + if (voiceState.SampleFormat == SampleFormat.PcmInt16) + { + dataSourceDetailType = PerformanceDetailType.PcmInt16; + } + else if (voiceState.SampleFormat == SampleFormat.PcmFloat) + { + dataSourceDetailType = PerformanceDetailType.PcmFloat; + } + + bool performanceInitialized = false; + + PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses(); + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, dataSourceDetailType, PerformanceEntryType.Voice, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateDataSource(ref voiceState, dspStateMemory, channelIndex); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + + if (voiceState.WasPlaying) + { + voiceState.PreviousVolume = 0.0f; + } + else if (voiceState.HasAnyDestination()) + { + performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.BiquadFilter, PerformanceEntryType.Voice, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateBiquadFilterForVoice(ref voiceState, dspStateMemory, (int)_rendererContext.MixBufferCount, channelIndex, nodeId); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + + performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.VolumeRamp, PerformanceEntryType.Voice, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + _commandBuffer.GenerateVolumeRamp(voiceState.PreviousVolume, + voiceState.Volume, + _rendererContext.MixBufferCount + (uint)channelIndex, + nodeId); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + + voiceState.PreviousVolume = voiceState.Volume; + + if (voiceState.MixId == Constants.UnusedMixId) + { + if (voiceState.SplitterId != Constants.UnusedSplitterId) + { + int destinationId = channelIndex; + + while (true) + { + Span destinationSpan = _splitterContext.GetDestination((int)voiceState.SplitterId, destinationId); + + if (destinationSpan.IsEmpty) + { + break; + } + + ref SplitterDestination destination = ref destinationSpan[0]; + + destinationId += (int)channelsCount; + + if (destination.IsConfigured()) + { + int mixId = destination.DestinationId; + + if (mixId < _mixContext.GetCount() && mixId != Constants.UnusedSplitterIdInt) + { + ref MixState mix = ref _mixContext.GetState(mixId); + + GenerateVoiceMix(destination.MixBufferVolume, + destination.PreviousMixBufferVolume, + dspStateMemory, + mix.BufferOffset, + mix.BufferCount, + _rendererContext.MixBufferCount + (uint)channelIndex, + nodeId); + + destination.MarkAsNeedToUpdateInternalState(); + } + } + } + } + } + else + { + ref MixState mix = ref _mixContext.GetState(voiceState.MixId); + + performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.Mix, PerformanceEntryType.Voice, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateVoiceMix(channelResource.Mix.AsSpan(), + channelResource.PreviousMix.AsSpan(), + dspStateMemory, + mix.BufferOffset, + mix.BufferCount, + _rendererContext.MixBufferCount + (uint)channelIndex, + nodeId); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + + channelResource.UpdateState(); + } + + for (int i = 0; i < voiceState.BiquadFilterNeedInitialization.Length; i++) + { + voiceState.BiquadFilterNeedInitialization[i] = voiceState.BiquadFilters[i].Enable; + } + } + } + } + + public void GenerateVoices() + { + for (int i = 0; i < _voiceContext.GetCount(); i++) + { + ref VoiceState sortedState = ref _voiceContext.GetSortedState(i); + + if (!sortedState.ShouldSkip() && sortedState.UpdateForCommandGeneration(_voiceContext)) + { + int nodeId = sortedState.NodeId; + + PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses(); + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.Voice, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateVoice(ref sortedState); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + } + + _splitterContext.UpdateInternalState(); + } + + public void GeneratePerformance(ref PerformanceEntryAddresses performanceEntryAddresses, PerformanceCommand.Type type, int nodeId) + { + _commandBuffer.GeneratePerformance(ref performanceEntryAddresses, type, nodeId); + } + + private void GenerateBufferMixerEffect(int bufferOffset, BufferMixEffect effect, int nodeId) + { + Debug.Assert(effect.Type == EffectType.BufferMix); + + if (effect.IsEnabled) + { + for (int i = 0; i < effect.Parameter.MixesCount; i++) + { + if (effect.Parameter.Volumes[i] != 0.0f) + { + _commandBuffer.GenerateMix((uint)bufferOffset + effect.Parameter.Input[i], + (uint)bufferOffset + effect.Parameter.Output[i], + nodeId, + effect.Parameter.Volumes[i]); + } + } + } + } + + private void GenerateAuxEffect(uint bufferOffset, AuxiliaryBufferEffect effect, int nodeId) + { + Debug.Assert(effect.Type == EffectType.AuxiliaryBuffer); + + if (effect.IsEnabled) + { + effect.GetWorkBuffer(0); + effect.GetWorkBuffer(1); + } + + if (effect.State.SendBufferInfoBase != 0 && effect.State.ReturnBufferInfoBase != 0) + { + int i = 0; + uint writeOffset = 0; + for (uint channelIndex = effect.Parameter.ChannelCount; channelIndex != 0; channelIndex--) + { + uint newUpdateCount = writeOffset + _commandBuffer.CommandList.SampleCount; + + uint updateCount; + + if (channelIndex != 1) + { + updateCount = 0; + } + else + { + updateCount = newUpdateCount; + } + + _commandBuffer.GenerateAuxEffect(bufferOffset, + effect.Parameter.Input[i], + effect.Parameter.Output[i], + ref effect.State, + effect.IsEnabled, + effect.Parameter.BufferStorageSize, + effect.State.SendBufferInfoBase, + effect.State.ReturnBufferInfoBase, + updateCount, + writeOffset, + nodeId); + + writeOffset = newUpdateCount; + + i++; + } + } + } + + private void GenerateDelayEffect(uint bufferOffset, DelayEffect effect, int nodeId, bool newEffectChannelMappingSupported) + { + Debug.Assert(effect.Type == EffectType.Delay); + + ulong workBuffer = effect.GetWorkBuffer(-1); + + _commandBuffer.GenerateDelayEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, newEffectChannelMappingSupported); + } + + private void GenerateReverbEffect(uint bufferOffset, ReverbEffect effect, int nodeId, bool isLongSizePreDelaySupported, bool newEffectChannelMappingSupported) + { + Debug.Assert(effect.Type == EffectType.Reverb); + + ulong workBuffer = effect.GetWorkBuffer(-1); + + _commandBuffer.GenerateReverbEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, isLongSizePreDelaySupported, newEffectChannelMappingSupported); + } + + private void GenerateReverb3dEffect(uint bufferOffset, Reverb3dEffect effect, int nodeId, bool newEffectChannelMappingSupported) + { + Debug.Assert(effect.Type == EffectType.Reverb3d); + + ulong workBuffer = effect.GetWorkBuffer(-1); + + _commandBuffer.GenerateReverb3dEffect(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId, newEffectChannelMappingSupported); + } + + private void GenerateBiquadFilterEffect(uint bufferOffset, BiquadFilterEffect effect, int nodeId) + { + Debug.Assert(effect.Type == EffectType.BiquadFilter); + + if (effect.IsEnabled) + { + bool needInitialization = effect.Parameter.Status == UsageState.Invalid || + (effect.Parameter.Status == UsageState.New && !_rendererContext.BehaviourContext.IsBiquadFilterEffectStateClearBugFixed()); + + BiquadFilterParameter parameter = new BiquadFilterParameter(); + + parameter.Enable = true; + effect.Parameter.Denominator.AsSpan().CopyTo(parameter.Denominator.AsSpan()); + effect.Parameter.Numerator.AsSpan().CopyTo(parameter.Numerator.AsSpan()); + + for (int i = 0; i < effect.Parameter.ChannelCount; i++) + { + _commandBuffer.GenerateBiquadFilter((int)bufferOffset, ref parameter, effect.State.Slice(i, 1), + effect.Parameter.Input[i], + effect.Parameter.Output[i], + needInitialization, + nodeId); + } + } + else + { + for (int i = 0; i < effect.Parameter.ChannelCount; i++) + { + uint inputBufferIndex = bufferOffset + effect.Parameter.Input[i]; + uint outputBufferIndex = bufferOffset + effect.Parameter.Output[i]; + + // If the input and output isn't the same, generate a command. + if (inputBufferIndex != outputBufferIndex) + { + _commandBuffer.GenerateCopyMixBuffer(inputBufferIndex, outputBufferIndex, nodeId); + } + } + } + } + + private void GenerateLimiterEffect(uint bufferOffset, LimiterEffect effect, int nodeId, int effectId) + { + Debug.Assert(effect.Type == EffectType.Limiter); + + ulong workBuffer = effect.GetWorkBuffer(-1); + + if (_rendererContext.BehaviourContext.IsEffectInfoVersion2Supported()) + { + Memory dspResultState; + + if (effect.Parameter.StatisticsEnabled) + { + dspResultState = _effectContext.GetDspStateMemory(effectId); + } + else + { + dspResultState = Memory.Empty; + } + + _commandBuffer.GenerateLimiterEffectVersion2(bufferOffset, effect.Parameter, effect.State, dspResultState, effect.IsEnabled, workBuffer, nodeId); + } + else + { + _commandBuffer.GenerateLimiterEffectVersion1(bufferOffset, effect.Parameter, effect.State, effect.IsEnabled, workBuffer, nodeId); + } + } + + private void GenerateCaptureEffect(uint bufferOffset, CaptureBufferEffect effect, int nodeId) + { + Debug.Assert(effect.Type == EffectType.CaptureBuffer); + + if (effect.IsEnabled) + { + effect.GetWorkBuffer(0); + } + + if (effect.State.SendBufferInfoBase != 0) + { + int i = 0; + uint writeOffset = 0; + + for (uint channelIndex = effect.Parameter.ChannelCount; channelIndex != 0; channelIndex--) + { + uint newUpdateCount = writeOffset + _commandBuffer.CommandList.SampleCount; + + uint updateCount; + + if (channelIndex != 1) + { + updateCount = 0; + } + else + { + updateCount = newUpdateCount; + } + + _commandBuffer.GenerateCaptureEffect(bufferOffset, + effect.Parameter.Input[i], + effect.State.SendBufferInfo, + effect.IsEnabled, + effect.Parameter.BufferStorageSize, + effect.State.SendBufferInfoBase, + updateCount, + writeOffset, + nodeId); + + writeOffset = newUpdateCount; + + i++; + } + } + } + + private void GenerateCompressorEffect(uint bufferOffset, CompressorEffect effect, int nodeId) + { + Debug.Assert(effect.Type == EffectType.Compressor); + + _commandBuffer.GenerateCompressorEffect(bufferOffset, + effect.Parameter, + effect.State, + effect.IsEnabled, + nodeId); + } + + private void GenerateEffect(ref MixState mix, int effectId, BaseEffect effect) + { + int nodeId = mix.NodeId; + + bool isFinalMix = mix.MixId == Constants.FinalMixId; + + PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses(); + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, effect.GetPerformanceDetailType(), + isFinalMix ? PerformanceEntryType.FinalMix : PerformanceEntryType.SubMix, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + switch (effect.Type) + { + case EffectType.BufferMix: + GenerateBufferMixerEffect((int)mix.BufferOffset, (BufferMixEffect)effect, nodeId); + break; + case EffectType.AuxiliaryBuffer: + GenerateAuxEffect(mix.BufferOffset, (AuxiliaryBufferEffect)effect, nodeId); + break; + case EffectType.Delay: + GenerateDelayEffect(mix.BufferOffset, (DelayEffect)effect, nodeId, _rendererContext.BehaviourContext.IsNewEffectChannelMappingSupported()); + break; + case EffectType.Reverb: + GenerateReverbEffect(mix.BufferOffset, (ReverbEffect)effect, nodeId, mix.IsLongSizePreDelaySupported, _rendererContext.BehaviourContext.IsNewEffectChannelMappingSupported()); + break; + case EffectType.Reverb3d: + GenerateReverb3dEffect(mix.BufferOffset, (Reverb3dEffect)effect, nodeId, _rendererContext.BehaviourContext.IsNewEffectChannelMappingSupported()); + break; + case EffectType.BiquadFilter: + GenerateBiquadFilterEffect(mix.BufferOffset, (BiquadFilterEffect)effect, nodeId); + break; + case EffectType.Limiter: + GenerateLimiterEffect(mix.BufferOffset, (LimiterEffect)effect, nodeId, effectId); + break; + case EffectType.CaptureBuffer: + GenerateCaptureEffect(mix.BufferOffset, (CaptureBufferEffect)effect, nodeId); + break; + case EffectType.Compressor: + GenerateCompressorEffect(mix.BufferOffset, (CompressorEffect)effect, nodeId); + break; + default: + throw new NotImplementedException($"Unsupported effect type {effect.Type}"); + } + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + + effect.UpdateForCommandGeneration(); + } + + private void GenerateEffects(ref MixState mix) + { + ReadOnlySpan effectProcessingOrderArray = mix.EffectProcessingOrderArray; + + Debug.Assert(_effectContext.GetCount() == 0 || !effectProcessingOrderArray.IsEmpty); + + for (int i = 0; i < _effectContext.GetCount(); i++) + { + int effectOrder = effectProcessingOrderArray[i]; + + if (effectOrder == Constants.InvalidProcessingOrder) + { + break; + } + + // BaseEffect is a class, we don't need to pass it by ref + BaseEffect effect = _effectContext.GetEffect(effectOrder); + + Debug.Assert(effect.Type != EffectType.Invalid); + Debug.Assert(effect.MixId == mix.MixId); + + if (!effect.ShouldSkip()) + { + GenerateEffect(ref mix, effectOrder, effect); + } + } + } + + private void GenerateMix(ref MixState mix) + { + if (mix.HasAnyDestination()) + { + Debug.Assert(mix.DestinationMixId != Constants.UnusedMixId || mix.DestinationSplitterId != Constants.UnusedSplitterId); + + if (mix.DestinationMixId == Constants.UnusedMixId) + { + if (mix.DestinationSplitterId != Constants.UnusedSplitterId) + { + int destinationId = 0; + + while (true) + { + int destinationIndex = destinationId++; + + Span destinationSpan = _splitterContext.GetDestination((int)mix.DestinationSplitterId, destinationIndex); + + if (destinationSpan.IsEmpty) + { + break; + } + + ref SplitterDestination destination = ref destinationSpan[0]; + + if (destination.IsConfigured()) + { + int mixId = destination.DestinationId; + + if (mixId < _mixContext.GetCount() && mixId != Constants.UnusedSplitterIdInt) + { + ref MixState destinationMix = ref _mixContext.GetState(mixId); + + uint inputBufferIndex = mix.BufferOffset + ((uint)destinationIndex % mix.BufferCount); + + for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++) + { + float volume = mix.Volume * destination.GetMixVolume((int)bufferDestinationIndex); + + if (volume != 0.0f) + { + _commandBuffer.GenerateMix(inputBufferIndex, + destinationMix.BufferOffset + bufferDestinationIndex, + mix.NodeId, + volume); + } + } + } + } + } + } + } + else + { + ref MixState destinationMix = ref _mixContext.GetState(mix.DestinationMixId); + + for (uint bufferIndex = 0; bufferIndex < mix.BufferCount; bufferIndex++) + { + for (uint bufferDestinationIndex = 0; bufferDestinationIndex < destinationMix.BufferCount; bufferDestinationIndex++) + { + float volume = mix.Volume * mix.GetMixBufferVolume((int)bufferIndex, (int)bufferDestinationIndex); + + if (volume != 0.0f) + { + _commandBuffer.GenerateMix(mix.BufferOffset + bufferIndex, + destinationMix.BufferOffset + bufferDestinationIndex, + mix.NodeId, + volume); + } + } + } + } + } + } + + private void GenerateSubMix(ref MixState subMix) + { + _commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer, + subMix.BufferOffset, + subMix.BufferCount, + subMix.NodeId, + subMix.SampleRate); + + GenerateEffects(ref subMix); + + PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses(); + + int nodeId = subMix.NodeId; + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.Mix, PerformanceEntryType.SubMix, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateMix(ref subMix); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + + public void GenerateSubMixes() + { + for (int id = 0; id < _mixContext.GetCount(); id++) + { + ref MixState sortedState = ref _mixContext.GetSortedState(id); + + if (sortedState.IsUsed && sortedState.MixId != Constants.FinalMixId) + { + int nodeId = sortedState.NodeId; + + PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses(); + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.SubMix, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateSubMix(ref sortedState); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + } + } + + private void GenerateFinalMix() + { + ref MixState finalMix = ref _mixContext.GetFinalState(); + + _commandBuffer.GenerateDepopForMixBuffersCommand(_rendererContext.DepopBuffer, + finalMix.BufferOffset, + finalMix.BufferCount, + finalMix.NodeId, + finalMix.SampleRate); + + GenerateEffects(ref finalMix); + + PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses(); + + int nodeId = finalMix.NodeId; + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.Mix, PerformanceEntryType.FinalMix, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + // Only generate volume command if the volume isn't 100%. + if (finalMix.Volume != 1.0f) + { + for (uint bufferIndex = 0; bufferIndex < finalMix.BufferCount; bufferIndex++) + { + bool performanceSubInitialized = false; + + if (_performanceManager != null && _performanceManager.IsTargetNodeId(nodeId) && _performanceManager.GetNextEntry(out performanceEntry, PerformanceDetailType.VolumeRamp, PerformanceEntryType.FinalMix, nodeId)) + { + performanceSubInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + _commandBuffer.GenerateVolume(finalMix.Volume, + finalMix.BufferOffset + bufferIndex, + nodeId); + + if (performanceSubInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + } + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + + public void GenerateFinalMixes() + { + int nodeId = _mixContext.GetFinalState().NodeId; + + PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses(); + + bool performanceInitialized = false; + + if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.FinalMix, nodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, nodeId); + } + + GenerateFinalMix(); + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, nodeId); + } + } + + private void GenerateCircularBuffer(CircularBufferSink sink, ref MixState finalMix) + { + _commandBuffer.GenerateCircularBuffer(finalMix.BufferOffset, sink, Constants.InvalidNodeId); + } + + private void GenerateDevice(DeviceSink sink, ref MixState finalMix) + { + if (_commandBuffer.CommandList.SampleRate != 48000 && sink.UpsamplerState == null) + { + sink.UpsamplerState = _rendererContext.UpsamplerManager.Allocate(); + } + + bool useCustomDownMixingCommand = _rendererContext.ChannelCount == 2 && sink.Parameter.DownMixParameterEnabled; + + if (useCustomDownMixingCommand) + { + _commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset, + sink.Parameter.Input.AsSpan(), + sink.Parameter.Input.AsSpan(), + sink.DownMixCoefficients, + Constants.InvalidNodeId); + } + // NOTE: We do the downmixing at the DSP level as it's easier that way. + else if (_rendererContext.ChannelCount == 2 && sink.Parameter.InputCount == 6) + { + _commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset, + sink.Parameter.Input.AsSpan(), + sink.Parameter.Input.AsSpan(), + Constants.DefaultSurroundToStereoCoefficients, + Constants.InvalidNodeId); + } + + CommandList commandList = _commandBuffer.CommandList; + + if (sink.UpsamplerState != null) + { + _commandBuffer.GenerateUpsample(finalMix.BufferOffset, + sink.UpsamplerState, + sink.Parameter.InputCount, + sink.Parameter.Input.AsSpan(), + commandList.BufferCount, + commandList.SampleCount, + commandList.SampleRate, + Constants.InvalidNodeId); + } + + _commandBuffer.GenerateDeviceSink(finalMix.BufferOffset, + sink, + _rendererContext.SessionId, + commandList.Buffers, + Constants.InvalidNodeId); + } + + private void GenerateSink(BaseSink sink, ref MixState finalMix) + { + bool performanceInitialized = false; + + PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses(); + + if (_performanceManager != null && _performanceManager.GetNextEntry(out performanceEntry, PerformanceEntryType.Sink, sink.NodeId)) + { + performanceInitialized = true; + + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.Start, sink.NodeId); + } + + if (!sink.ShouldSkip) + { + switch (sink.Type) + { + case SinkType.CircularBuffer: + GenerateCircularBuffer((CircularBufferSink)sink, ref finalMix); + break; + case SinkType.Device: + GenerateDevice((DeviceSink)sink, ref finalMix); + break; + default: + throw new NotImplementedException($"Unsupported sink type {sink.Type}"); + } + + sink.UpdateForCommandGeneration(); + } + + if (performanceInitialized) + { + GeneratePerformance(ref performanceEntry, PerformanceCommand.Type.End, sink.NodeId); + } + } + + public void GenerateSinks() + { + ref MixState finalMix = ref _mixContext.GetFinalState(); + + for (int i = 0; i < _sinkContext.GetCount(); i++) + { + // BaseSink is a class, we don't need to pass it by ref + BaseSink sink = _sinkContext.GetSink(i); + + if (sink.IsUsed) + { + GenerateSink(sink, ref finalMix); + } + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs new file mode 100644 index 00000000..63dc9ca9 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs @@ -0,0 +1,188 @@ +using Ryujinx.Audio.Renderer.Dsp.Command; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// version 1. + /// + public class CommandProcessingTimeEstimatorVersion1 : ICommandProcessingTimeEstimator + { + private uint _sampleCount; + private uint _bufferCount; + + public CommandProcessingTimeEstimatorVersion1(uint sampleCount, uint bufferCount) + { + _sampleCount = sampleCount; + _bufferCount = bufferCount; + } + + public uint Estimate(PerformanceCommand command) + { + return 1454; + } + + public uint Estimate(ClearMixBufferCommand command) + { + return (uint)(_sampleCount * 0.83f * _bufferCount * 1.2f); + } + + public uint Estimate(BiquadFilterCommand command) + { + return (uint)(_sampleCount * 58.0f * 1.2f); + } + + public uint Estimate(MixRampGroupedCommand command) + { + int volumeCount = 0; + + for (int i = 0; i < command.MixBufferCount; i++) + { + if (command.Volume0[i] != 0.0f || command.Volume1[i] != 0.0f) + { + volumeCount++; + } + } + + return (uint)(_sampleCount * 14.4f * 1.2f * volumeCount); + } + + public uint Estimate(MixRampCommand command) + { + return (uint)(_sampleCount * 14.4f * 1.2f); + } + + public uint Estimate(DepopPrepareCommand command) + { + return 1080; + } + + public uint Estimate(VolumeRampCommand command) + { + return (uint)(_sampleCount * 9.8f * 1.2f); + } + + public uint Estimate(PcmInt16DataSourceCommandVersion1 command) + { + return (uint)(command.Pitch * 0.25f * 1.2f); + } + + public uint Estimate(AdpcmDataSourceCommandVersion1 command) + { + return (uint)(command.Pitch * 0.46f * 1.2f); + } + + public uint Estimate(DepopForMixBuffersCommand command) + { + return (uint)(_sampleCount * 8.9f * command.MixBufferCount); + } + + public uint Estimate(CopyMixBufferCommand command) + { + // NOTE: Nintendo returns 0 here for some reasons even if it will generate a command like that on version 1.. maybe a mistake? + return 0; + } + + public uint Estimate(MixCommand command) + { + return (uint)(_sampleCount * 10.0f * 1.2f); + } + + public uint Estimate(DelayCommand command) + { + return (uint)(_sampleCount * command.Parameter.ChannelCount * 202.5f); + } + + public uint Estimate(ReverbCommand command) + { + Debug.Assert(command.Parameter.IsChannelCountValid()); + + if (command.Enabled) + { + return (uint)(750 * _sampleCount * command.Parameter.ChannelCount * 1.2f); + } + + return 0; + } + + public uint Estimate(Reverb3dCommand command) + { + if (command.Enabled) + { + return (uint)(530 * _sampleCount * command.Parameter.ChannelCount * 1.2f); + } + + return 0; + } + + public uint Estimate(AuxiliaryBufferCommand command) + { + if (command.Enabled) + { + return 15956; + } + + return 3765; + } + + public uint Estimate(VolumeCommand command) + { + return (uint)(_sampleCount * 8.8f * 1.2f); + } + + public uint Estimate(CircularBufferSinkCommand command) + { + return 55; + } + + public uint Estimate(DownMixSurroundToStereoCommand command) + { + return 16108; + } + + public uint Estimate(UpsampleCommand command) + { + return 357915; + } + + public uint Estimate(DeviceSinkCommand command) + { + return 10042; + } + + public uint Estimate(PcmFloatDataSourceCommandVersion1 command) + { + return 0; + } + + public uint Estimate(DataSourceVersion2Command command) + { + return 0; + } + + public uint Estimate(LimiterCommandVersion1 command) + { + return 0; + } + + public uint Estimate(LimiterCommandVersion2 command) + { + return 0; + } + + public uint Estimate(GroupedBiquadFilterCommand command) + { + return 0; + } + + public uint Estimate(CaptureBufferCommand command) + { + return 0; + } + + public uint Estimate(CompressorCommand command) + { + return 0; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs new file mode 100644 index 00000000..7ee491cd --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs @@ -0,0 +1,552 @@ +using Ryujinx.Audio.Renderer.Dsp.Command; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// version 2. (added with REV5) + /// + public class CommandProcessingTimeEstimatorVersion2 : ICommandProcessingTimeEstimator + { + private uint _sampleCount; + private uint _bufferCount; + + public CommandProcessingTimeEstimatorVersion2(uint sampleCount, uint bufferCount) + { + _sampleCount = sampleCount; + _bufferCount = bufferCount; + } + + public uint Estimate(PerformanceCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)489.35f; + } + + return (uint)491.18f; + } + + public uint Estimate(ClearMixBufferCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerBuffer = 668.8f; + float baseCost = 193.2f; + + if (_sampleCount == 160) + { + costPerBuffer = 260.4f; + baseCost = 139.65f; + } + + return (uint)(baseCost + costPerBuffer * _bufferCount); + } + + public uint Estimate(BiquadFilterCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)4813.2f; + } + + return (uint)6915.4f; + } + + public uint Estimate(MixRampGroupedCommand command) + { + const float costPerSample = 7.245f; + + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + int volumeCount = 0; + + for (int i = 0; i < command.MixBufferCount; i++) + { + if (command.Volume0[i] != 0.0f || command.Volume1[i] != 0.0f) + { + volumeCount++; + } + } + + return (uint)(_sampleCount * costPerSample * volumeCount); + } + + public uint Estimate(MixRampCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1859.0f; + } + + return (uint)2286.1f; + } + + public uint Estimate(DepopPrepareCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)306.62f; + } + + return (uint)293.22f; + } + + public uint Estimate(VolumeRampCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1403.9f; + } + + return (uint)1884.3f; + } + + public uint Estimate(PcmInt16DataSourceCommandVersion1 command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerSample = 1195.5f; + float baseCost = 7797.0f; + + if (_sampleCount == 160) + { + costPerSample = 749.27f; + baseCost = 6138.9f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(AdpcmDataSourceCommandVersion1 command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerSample = 3564.1f; + float baseCost = 6225.5f; + + if (_sampleCount == 160) + { + costPerSample = 2125.6f; + baseCost = 9039.5f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(DepopForMixBuffersCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)762.96f; + } + + return (uint)726.96f; + } + + public uint Estimate(CopyMixBufferCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)836.32f; + } + + return (uint)1000.9f; + } + + public uint Estimate(MixCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1342.2f; + } + + return (uint)1833.2f; + } + + public uint Estimate(DelayCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)41636.0f; + case 2: + return (uint)97861.0f; + case 4: + return (uint)192520.0f; + case 6: + return (uint)301760.0f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)578.53f; + case 2: + return (uint)663.06f; + case 4: + return (uint)703.98f; + case 6: + return (uint)760.03f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + + } + + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)8770.3f; + case 2: + return (uint)25741.0f; + case 4: + return (uint)47551.0f; + case 6: + return (uint)81629.0f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)521.28f; + case 2: + return (uint)585.4f; + case 4: + return (uint)629.88f; + case 6: + return (uint)713.57f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + public uint Estimate(ReverbCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)97192.0f; + case 2: + return (uint)103280.0f; + case 4: + return (uint)109580.0f; + case 6: + return (uint)115070.0f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)492.01f; + case 2: + return (uint)554.46f; + case 4: + return (uint)595.86f; + case 6: + return (uint)656.62f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + + } + + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)136460.0f; + case 2: + return (uint)145750.0f; + case 4: + return (uint)154800.0f; + case 6: + return (uint)161970.0f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)495.79f; + case 2: + return (uint)527.16f; + case 4: + return (uint)598.75f; + case 6: + return (uint)666.03f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + public uint Estimate(Reverb3dCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)138840.0f; + case 2: + return (uint)135430.0f; + case 4: + return (uint)199180.0f; + case 6: + return (uint)247350.0f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)718.7f; + case 2: + return (uint)751.3f; + case 4: + return (uint)797.46f; + case 6: + return (uint)867.43f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)199950.0f; + case 2: + return (uint)195200.0f; + case 4: + return (uint)290580.0f; + case 6: + return (uint)363490.0f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)534.24f; + case 2: + return (uint)570.87f; + case 4: + return (uint)660.93f; + case 6: + return (uint)694.6f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + public uint Estimate(AuxiliaryBufferCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + return (uint)7177.9f; + } + + return (uint)489.16f; + } + + if (command.Enabled) + { + return (uint)9499.8f; + } + + return (uint)485.56f; + } + + public uint Estimate(VolumeCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1280.3f; + } + + return (uint)1737.8f; + } + + public uint Estimate(CircularBufferSinkCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerBuffer = 1726.0f; + float baseCost = 1369.7f; + + if (_sampleCount == 160) + { + costPerBuffer = 853.63f; + baseCost = 1284.5f; + } + + return (uint)(baseCost + costPerBuffer * command.InputCount); + } + + public uint Estimate(DownMixSurroundToStereoCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)10009.0f; + } + + return (uint)14577.0f; + } + + public uint Estimate(UpsampleCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)292000.0f; + } + + return (uint)0.0f; + } + + public uint Estimate(DeviceSinkCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + Debug.Assert(command.InputCount == 2 || command.InputCount == 6); + + if (command.InputCount == 2) + { + if (_sampleCount == 160) + { + return (uint)9261.5f; + } + + return (uint)9336.1f; + } + + if (_sampleCount == 160) + { + return (uint)9111.8f; + } + + return (uint)9566.7f; + } + + public uint Estimate(PcmFloatDataSourceCommandVersion1 command) + { + // NOTE: This was added between REV7 and REV8 and for some reasons the estimator v2 was changed... + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerSample = 3490.9f; + float baseCost = 10091.0f; + + if (_sampleCount == 160) + { + costPerSample = 2310.4f; + baseCost = 7845.3f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(DataSourceVersion2Command command) + { + return 0; + } + + public uint Estimate(LimiterCommandVersion1 command) + { + return 0; + } + + public uint Estimate(LimiterCommandVersion2 command) + { + return 0; + } + + public uint Estimate(GroupedBiquadFilterCommand command) + { + return 0; + } + + public uint Estimate(CaptureBufferCommand command) + { + return 0; + } + + public uint Estimate(CompressorCommand command) + { + return 0; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs new file mode 100644 index 00000000..b79ca136 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs @@ -0,0 +1,756 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Dsp.Command; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using System; +using System.Diagnostics; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// version 3. (added with REV8) + /// + public class CommandProcessingTimeEstimatorVersion3 : ICommandProcessingTimeEstimator + { + protected uint _sampleCount; + protected uint _bufferCount; + + public CommandProcessingTimeEstimatorVersion3(uint sampleCount, uint bufferCount) + { + _sampleCount = sampleCount; + _bufferCount = bufferCount; + } + + public uint Estimate(PerformanceCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)498.17f; + } + + return (uint)489.42f; + } + + public uint Estimate(ClearMixBufferCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerBuffer = 440.68f; + float baseCost = 0; + + if (_sampleCount == 160) + { + costPerBuffer = 266.65f; + } + + return (uint)(baseCost + costPerBuffer * _bufferCount); + } + + public uint Estimate(BiquadFilterCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)4173.2f; + } + + return (uint)5585.1f; + } + + public uint Estimate(MixRampGroupedCommand command) + { + float costPerSample = 6.4434f; + + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + costPerSample = 6.708f; + } + + int volumeCount = 0; + + for (int i = 0; i < command.MixBufferCount; i++) + { + if (command.Volume0[i] != 0.0f || command.Volume1[i] != 0.0f) + { + volumeCount++; + } + } + + return (uint)(_sampleCount * costPerSample * volumeCount); + } + + public uint Estimate(MixRampCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1968.7f; + } + + return (uint)2459.4f; + } + + public uint Estimate(DepopPrepareCommand command) + { + return 0; + } + + public uint Estimate(VolumeRampCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1425.3f; + } + + return (uint)1700.0f; + } + + public uint Estimate(PcmInt16DataSourceCommandVersion1 command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerSample = 710.143f; + float baseCost = 7853.286f; + + if (_sampleCount == 160) + { + costPerSample = 427.52f; + baseCost = 6329.442f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(AdpcmDataSourceCommandVersion1 command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerSample = 3564.1f; + float baseCost = 9736.702f; + + if (_sampleCount == 160) + { + costPerSample = 2125.6f; + baseCost = 7913.808f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(DepopForMixBuffersCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)739.64f; + } + + return (uint)910.97f; + } + + public uint Estimate(CopyMixBufferCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)842.59f; + } + + return (uint)986.72f; + } + + public uint Estimate(MixCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1402.8f; + } + + return (uint)1853.2f; + } + + public virtual uint Estimate(DelayCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)8929.04f; + case 2: + return (uint)25500.75f; + case 4: + return (uint)47759.62f; + case 6: + return (uint)82203.07f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)1295.20f; + case 2: + return (uint)1213.60f; + case 4: + return (uint)942.03f; + case 6: + return (uint)1001.55f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)11941.05f; + case 2: + return (uint)37197.37f; + case 4: + return (uint)69749.84f; + case 6: + return (uint)120042.40f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)997.67f; + case 2: + return (uint)977.63f; + case 4: + return (uint)792.30f; + case 6: + return (uint)875.43f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + public virtual uint Estimate(ReverbCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)81475.05f; + case 2: + return (uint)84975.0f; + case 4: + return (uint)91625.15f; + case 6: + return (uint)95332.27f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)536.30f; + case 2: + return (uint)588.70f; + case 4: + return (uint)643.70f; + case 6: + return (uint)706.0f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)120174.47f; + case 2: + return (uint)25262.22f; + case 4: + return (uint)135751.23f; + case 6: + return (uint)141129.23f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)617.64f; + case 2: + return (uint)659.54f; + case 4: + return (uint)711.43f; + case 6: + return (uint)778.07f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + public virtual uint Estimate(Reverb3dCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)116754.0f; + case 2: + return (uint)125912.05f; + case 4: + return (uint)146336.03f; + case 6: + return (uint)165812.66f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)734.0f; + case 2: + return (uint)766.62f; + case 4: + return (uint)797.46f; + case 6: + return (uint)867.43f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)170292.34f; + case 2: + return (uint)183875.63f; + case 4: + return (uint)214696.19f; + case 6: + return (uint)243846.77f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)508.47f; + case 2: + return (uint)582.45f; + case 4: + return (uint)626.42f; + case 6: + return (uint)682.47f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + public uint Estimate(AuxiliaryBufferCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + return (uint)7182.14f; + } + + return (uint)472.11f; + } + + if (command.Enabled) + { + return (uint)9435.96f; + } + + return (uint)462.62f; + } + + public uint Estimate(VolumeCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)1311.1f; + } + + return (uint)1713.6f; + } + + public uint Estimate(CircularBufferSinkCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerBuffer = 770.26f; + float baseCost = 0f; + + if (_sampleCount == 160) + { + costPerBuffer = 531.07f; + } + + return (uint)(baseCost + costPerBuffer * command.InputCount); + } + + public uint Estimate(DownMixSurroundToStereoCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)9949.7f; + } + + return (uint)14679.0f; + } + + public uint Estimate(UpsampleCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)312990.0f; + } + + return (uint)0.0f; + } + + public uint Estimate(DeviceSinkCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + Debug.Assert(command.InputCount == 2 || command.InputCount == 6); + + if (command.InputCount == 2) + { + if (_sampleCount == 160) + { + return (uint)8980.0f; + } + + return (uint)9221.9f; + } + + if (_sampleCount == 160) + { + return (uint)9177.9f; + } + + return (uint)9725.9f; + } + + public uint Estimate(PcmFloatDataSourceCommandVersion1 command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + float costPerSample = 3490.9f; + float baseCost = 10090.9f; + + if (_sampleCount == 160) + { + costPerSample = 2310.4f; + baseCost = 7845.25f; + } + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f)))); + } + + public uint Estimate(DataSourceVersion2Command command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + (float baseCost, float costPerSample) = GetCostByFormat(_sampleCount, command.SampleFormat, command.SrcQuality); + + return (uint)(baseCost + (costPerSample * (((command.SampleRate / 200.0f) / _sampleCount) * (command.Pitch * 0.000030518f) - 1.0f))); + } + + private static (float, float) GetCostByFormat(uint sampleCount, SampleFormat format, SampleRateConversionQuality quality) + { + Debug.Assert(sampleCount == 160 || sampleCount == 240); + + switch (format) + { + case SampleFormat.PcmInt16: + switch (quality) + { + case SampleRateConversionQuality.Default: + if (sampleCount == 160) + { + return (6329.44f, 427.52f); + } + + return (7853.28f, 710.14f); + case SampleRateConversionQuality.High: + if (sampleCount == 160) + { + return (8049.42f, 371.88f); + } + + return (10138.84f, 610.49f); + case SampleRateConversionQuality.Low: + if (sampleCount == 160) + { + return (5062.66f, 423.43f); + } + + return (5810.96f, 676.72f); + default: + throw new NotImplementedException($"{format} {quality}"); + } + case SampleFormat.PcmFloat: + switch (quality) + { + case SampleRateConversionQuality.Default: + if (sampleCount == 160) + { + return (7845.25f, 2310.4f); + } + + return (10090.9f, 3490.9f); + case SampleRateConversionQuality.High: + if (sampleCount == 160) + { + return (9446.36f, 2308.91f); + } + + return (12520.85f, 3480.61f); + case SampleRateConversionQuality.Low: + if (sampleCount == 160) + { + return (9446.36f, 2308.91f); + } + + return (12520.85f, 3480.61f); + default: + throw new NotImplementedException($"{format} {quality}"); + } + case SampleFormat.Adpcm: + switch (quality) + { + case SampleRateConversionQuality.Default: + if (sampleCount == 160) + { + return (7913.81f, 1827.66f); + } + + return (9736.70f, 2756.37f); + case SampleRateConversionQuality.High: + if (sampleCount == 160) + { + return (9607.81f, 1829.29f); + } + + return (12154.38f, 2731.31f); + case SampleRateConversionQuality.Low: + if (sampleCount == 160) + { + return (6517.48f, 1824.61f); + } + + return (7929.44f, 2732.15f); + default: + throw new NotImplementedException($"{format} {quality}"); + } + default: + throw new NotImplementedException($"{format}"); + } + } + + private uint EstimateLimiterCommandCommon(LimiterParameter parameter, bool enabled) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (enabled) + { + switch (parameter.ChannelCount) + { + case 1: + return (uint)21392.0f; + case 2: + return (uint)26829.0f; + case 4: + return (uint)32405.0f; + case 6: + return (uint)52219.0f; + default: + throw new NotImplementedException($"{parameter.ChannelCount}"); + } + } + else + { + switch (parameter.ChannelCount) + { + case 1: + return (uint)897.0f; + case 2: + return (uint)931.55f; + case 4: + return (uint)975.39f; + case 6: + return (uint)1016.8f; + default: + throw new NotImplementedException($"{parameter.ChannelCount}"); + } + } + } + + if (enabled) + { + switch (parameter.ChannelCount) + { + case 1: + return (uint)30556.0f; + case 2: + return (uint)39011.0f; + case 4: + return (uint)48270.0f; + case 6: + return (uint)76712.0f; + default: + throw new NotImplementedException($"{parameter.ChannelCount}"); + } + } + else + { + switch (parameter.ChannelCount) + { + case 1: + return (uint)874.43f; + case 2: + return (uint)921.55f; + case 4: + return (uint)945.26f; + case 6: + return (uint)992.26f; + default: + throw new NotImplementedException($"{parameter.ChannelCount}"); + } + } + } + + public uint Estimate(LimiterCommandVersion1 command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + return EstimateLimiterCommandCommon(command.Parameter, command.IsEffectEnabled); + } + + public uint Estimate(LimiterCommandVersion2 command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (!command.Parameter.StatisticsEnabled || !command.IsEffectEnabled) + { + return EstimateLimiterCommandCommon(command.Parameter, command.IsEffectEnabled); + } + + if (_sampleCount == 160) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)23309.0f; + case 2: + return (uint)29954.0f; + case 4: + return (uint)35807.0f; + case 6: + return (uint)58340.0f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)33526.0f; + case 2: + return (uint)43549.0f; + case 4: + return (uint)52190.0f; + case 6: + return (uint)85527.0f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + + public virtual uint Estimate(GroupedBiquadFilterCommand command) + { + return 0; + } + + public virtual uint Estimate(CaptureBufferCommand command) + { + return 0; + } + + public virtual uint Estimate(CompressorCommand command) + { + return 0; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs new file mode 100644 index 00000000..c60d8ebc --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion4.cs @@ -0,0 +1,47 @@ +using Ryujinx.Audio.Renderer.Dsp.Command; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// version 4. (added with REV10) + /// + public class CommandProcessingTimeEstimatorVersion4 : CommandProcessingTimeEstimatorVersion3 + { + public CommandProcessingTimeEstimatorVersion4(uint sampleCount, uint bufferCount) : base(sampleCount, bufferCount) { } + + public override uint Estimate(GroupedBiquadFilterCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + return (uint)7424.5f; + } + + return (uint)9730.4f; + } + + public override uint Estimate(CaptureBufferCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + return (uint)435.2f; + } + + return (uint)4261.0f; + } + + if (command.Enabled) + { + return (uint)5858.26f; + } + + return (uint)435.2f; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs new file mode 100644 index 00000000..2ed7e6a5 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion5.cs @@ -0,0 +1,310 @@ +using Ryujinx.Audio.Renderer.Dsp.Command; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// version 5. (added with REV11) + /// + public class CommandProcessingTimeEstimatorVersion5 : CommandProcessingTimeEstimatorVersion4 + { + public CommandProcessingTimeEstimatorVersion5(uint sampleCount, uint bufferCount) : base(sampleCount, bufferCount) { } + + public override uint Estimate(DelayCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return 8929; + case 2: + return 25501; + case 4: + return 47760; + case 6: + return 82203; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)1295.20f; + case 2: + return (uint)1213.60f; + case 4: + return (uint)942.03f; + case 6: + return (uint)1001.6f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return 11941; + case 2: + return 37197; + case 4: + return 69750; + case 6: + return 12004; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)997.67f; + case 2: + return (uint)977.63f; + case 4: + return (uint)792.31f; + case 6: + return (uint)875.43f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + public override uint Estimate(ReverbCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return 81475; + case 2: + return 84975; + case 4: + return 91625; + case 6: + return 95332; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)536.30f; + case 2: + return (uint)588.80f; + case 4: + return (uint)643.70f; + case 6: + return (uint)706.0f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return 120170; + case 2: + return 125260; + case 4: + return 135750; + case 6: + return 141130; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)617.64f; + case 2: + return (uint)659.54f; + case 4: + return (uint)711.44f; + case 6: + return (uint)778.07f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + public override uint Estimate(Reverb3dCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return 116750; + case 2: + return 125910; + case 4: + return 146340; + case 6: + return 165810; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return 735; + case 2: + return (uint)766.62f; + case 4: + return (uint)834.07f; + case 6: + return (uint)875.44f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return 170290; + case 2: + return 183880; + case 4: + return 214700; + case 6: + return 243850; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)508.47f; + case 2: + return (uint)582.45f; + case 4: + return (uint)626.42f; + case 6: + return (uint)682.47f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + public override uint Estimate(CompressorCommand command) + { + Debug.Assert(_sampleCount == 160 || _sampleCount == 240); + + if (_sampleCount == 160) + { + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return 34431; + case 2: + return 44253; + case 4: + return 63827; + case 6: + return 83361; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)630.12f; + case 2: + return (uint)638.27f; + case 4: + return (uint)705.86f; + case 6: + return (uint)782.02f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + + if (command.Enabled) + { + switch (command.Parameter.ChannelCount) + { + case 1: + return 51095; + case 2: + return 65693; + case 4: + return 95383; + case 6: + return 124510; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + else + { + switch (command.Parameter.ChannelCount) + { + case 1: + return (uint)840.14f; + case 2: + return (uint)826.1f; + case 4: + return (uint)901.88f; + case 6: + return (uint)965.29f; + default: + throw new NotImplementedException($"{command.Parameter.ChannelCount}"); + } + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs new file mode 100644 index 00000000..16406527 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs @@ -0,0 +1,85 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Renderer.Dsp.State.AuxiliaryBufferHeader; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for an auxiliary buffer effect. + /// + public class AuxiliaryBufferEffect : BaseEffect + { + /// + /// The auxiliary buffer parameter. + /// + public AuxiliaryBufferParameter Parameter; + + /// + /// Auxiliary buffer state. + /// + public AuxiliaryBufferAddresses State; + + public override EffectType TargetEffectType => EffectType.AuxiliaryBuffer; + + public override DspAddress GetWorkBuffer(int index) + { + return WorkBuffers[index].GetReference(true); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(ref parameter)); + + UpdateParameterBase(ref parameter); + + Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; + IsEnabled = parameter.IsEnabled; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + if (BufferUnmapped || parameter.IsNew) + { + ulong bufferSize = (ulong)Unsafe.SizeOf() * Parameter.BufferStorageSize + (ulong)Unsafe.SizeOf(); + + bool sendBufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], Parameter.SendBufferInfoAddress, bufferSize); + bool returnBufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[1], Parameter.ReturnBufferInfoAddress, bufferSize); + + BufferUnmapped = sendBufferUnmapped && returnBufferUnmapped; + + if (!BufferUnmapped) + { + DspAddress sendDspAddress = WorkBuffers[0].GetReference(false); + DspAddress returnDspAddress = WorkBuffers[1].GetReference(false); + + State.SendBufferInfo = sendDspAddress + (uint)Unsafe.SizeOf(); + State.SendBufferInfoBase = sendDspAddress + (uint)Unsafe.SizeOf(); + + State.ReturnBufferInfo = returnDspAddress + (uint)Unsafe.SizeOf(); + State.ReturnBufferInfoBase = returnDspAddress + (uint)Unsafe.SizeOf(); + } + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs new file mode 100644 index 00000000..825b3bf7 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs @@ -0,0 +1,272 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Diagnostics; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Base class used as a server state for an effect. + /// + public class BaseEffect + { + /// + /// The of the effect. + /// + public EffectType Type; + + /// + /// Set to true if the effect must be active. + /// + public bool IsEnabled; + + /// + /// Set to true if the internal effect work buffers used wasn't mapped. + /// + public bool BufferUnmapped; + + /// + /// The current state of the effect. + /// + public UsageState UsageState; + + /// + /// The target mix id of the effect. + /// + public int MixId; + + /// + /// Position of the effect while processing effects. + /// + public uint ProcessingOrder; + + /// + /// Array of all the work buffer used by the effect. + /// + protected AddressInfo[] WorkBuffers; + + /// + /// Create a new . + /// + public BaseEffect() + { + Type = TargetEffectType; + UsageState = UsageState.Invalid; + + IsEnabled = false; + BufferUnmapped = false; + MixId = Constants.UnusedMixId; + ProcessingOrder = uint.MaxValue; + + WorkBuffers = new AddressInfo[2]; + + foreach (ref AddressInfo info in WorkBuffers.AsSpan()) + { + info = AddressInfo.Create(); + } + } + + /// + /// The target handled by this . + /// + public virtual EffectType TargetEffectType => EffectType.Invalid; + + /// + /// Check if the sent by the user match the internal . + /// + /// The user parameter. + /// Returns true if the sent by the user matches the internal . + public bool IsTypeValid(ref T parameter) where T : unmanaged, IEffectInParameter + { + return parameter.Type == TargetEffectType; + } + + /// + /// Update the usage state during command generation. + /// + protected void UpdateUsageStateForCommandGeneration() + { + UsageState = IsEnabled ? UsageState.Enabled : UsageState.Disabled; + } + + /// + /// Update the internal common parameters from a user parameter. + /// + /// The user parameter. + protected void UpdateParameterBase(ref T parameter) where T : unmanaged, IEffectInParameter + { + MixId = parameter.MixId; + ProcessingOrder = parameter.ProcessingOrder; + } + + /// + /// Force unmap all the work buffers. + /// + /// The mapper to use. + public void ForceUnmapBuffers(PoolMapper mapper) + { + foreach (ref AddressInfo info in WorkBuffers.AsSpan()) + { + if (info.GetReference(false) != 0) + { + mapper.ForceUnmap(ref info); + } + } + } + + /// + /// Check if the effect needs to be skipped. + /// + /// Returns true if the effect needs to be skipped. + public bool ShouldSkip() + { + return BufferUnmapped; + } + + /// + /// Update the state during command generation. + /// + public virtual void UpdateForCommandGeneration() + { + Debug.Assert(Type == TargetEffectType); + } + + /// + /// Initialize the given result state. + /// + /// The state to initalize + public virtual void InitializeResultState(ref EffectResultState state) { } + + /// + /// Update the result state with . + /// + /// The destination result state + /// The source result state + public virtual void UpdateResultState(ref EffectResultState destState, ref EffectResultState srcState) { } + + /// + /// Update the internal state from a user version 1 parameter. + /// + /// The possible that was generated. + /// The user parameter. + /// The mapper to use. + public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(ref parameter)); + + updateErrorInfo = new ErrorInfo(); + } + + /// + /// Update the internal state from a user version 2 parameter. + /// + /// The possible that was generated. + /// The user parameter. + /// The mapper to use. + public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(ref parameter)); + + updateErrorInfo = new ErrorInfo(); + } + + /// + /// Get the work buffer DSP address at the given index. + /// + /// The index of the work buffer + /// The work buffer DSP address at the given index. + public virtual DspAddress GetWorkBuffer(int index) + { + throw new InvalidOperationException(); + } + + /// + /// Get the first work buffer DSP address. + /// + /// The first work buffer DSP address. + protected DspAddress GetSingleBuffer() + { + if (IsEnabled) + { + return WorkBuffers[0].GetReference(true); + } + + if (UsageState != UsageState.Disabled) + { + DspAddress address = WorkBuffers[0].GetReference(false); + ulong size = WorkBuffers[0].Size; + + if (address != 0 && size != 0) + { + AudioProcessorMemoryManager.InvalidateDataCache(address, size); + } + } + + return 0; + } + + /// + /// Store the output status to the given user output. + /// + /// The given user output. + /// If set to true, the is active. + public void StoreStatus(ref T outStatus, bool isAudioRendererActive) where T : unmanaged, IEffectOutStatus + { + if (isAudioRendererActive) + { + if (UsageState == UsageState.Disabled) + { + outStatus.State = EffectState.Disabled; + } + else + { + outStatus.State = EffectState.Enabled; + } + } + else if (UsageState == UsageState.New) + { + outStatus.State = EffectState.Enabled; + } + else + { + outStatus.State = EffectState.Disabled; + } + } + + /// + /// Get the associated to the of this effect. + /// + /// The associated to the of this effect. + public PerformanceDetailType GetPerformanceDetailType() + { + switch (Type) + { + case EffectType.BiquadFilter: + return PerformanceDetailType.BiquadFilter; + case EffectType.AuxiliaryBuffer: + return PerformanceDetailType.Aux; + case EffectType.Delay: + return PerformanceDetailType.Delay; + case EffectType.Reverb: + return PerformanceDetailType.Reverb; + case EffectType.Reverb3d: + return PerformanceDetailType.Reverb3d; + case EffectType.BufferMix: + return PerformanceDetailType.Mix; + case EffectType.Limiter: + return PerformanceDetailType.Limiter; + case EffectType.CaptureBuffer: + return PerformanceDetailType.CaptureBuffer; + case EffectType.Compressor: + return PerformanceDetailType.Compressor; + default: + throw new NotImplementedException($"{Type}"); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs new file mode 100644 index 00000000..de91046d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs @@ -0,0 +1,67 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a biquad filter effect. + /// + public class BiquadFilterEffect : BaseEffect + { + /// + /// The biquad filter parameter. + /// + public BiquadFilterEffectParameter Parameter; + + /// + /// The biquad filter state. + /// + public Memory State { get; } + + /// + /// Create a new . + /// + public BiquadFilterEffect() + { + Parameter = new BiquadFilterEffectParameter(); + State = new BiquadFilterState[Constants.ChannelCountMax]; + } + + public override EffectType TargetEffectType => EffectType.BiquadFilter; + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(ref parameter)); + + UpdateParameterBase(ref parameter); + + Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; + IsEnabled = parameter.IsEnabled; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.Status = UsageState.Enabled; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs new file mode 100644 index 00000000..82c0a055 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs @@ -0,0 +1,49 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a buffer mix effect. + /// + public class BufferMixEffect : BaseEffect + { + /// + /// The buffer mix parameter. + /// + public BufferMixParameter Parameter; + + public override EffectType TargetEffectType => EffectType.BufferMix; + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(ref parameter)); + + UpdateParameterBase(ref parameter); + + Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; + IsEnabled = parameter.IsEnabled; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs new file mode 100644 index 00000000..c445798d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs @@ -0,0 +1,82 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for an capture buffer effect. + /// + public class CaptureBufferEffect : BaseEffect + { + /// + /// The capture buffer parameter. + /// + public AuxiliaryBufferParameter Parameter; + + /// + /// Capture buffer state. + /// + public AuxiliaryBufferAddresses State; + + public override EffectType TargetEffectType => EffectType.CaptureBuffer; + + public override DspAddress GetWorkBuffer(int index) + { + return WorkBuffers[index].GetReference(true); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(ref parameter)); + + UpdateParameterBase(ref parameter); + + Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; + IsEnabled = parameter.IsEnabled; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + if (BufferUnmapped || parameter.IsNew) + { + ulong bufferSize = (ulong)Unsafe.SizeOf() * Parameter.BufferStorageSize + (ulong)Unsafe.SizeOf(); + + bool sendBufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], Parameter.SendBufferInfoAddress, bufferSize); + + BufferUnmapped = sendBufferUnmapped; + + if (!BufferUnmapped) + { + DspAddress sendDspAddress = WorkBuffers[0].GetReference(false); + + // NOTE: Nintendo directly interact with the CPU side structure in the processing of the DSP command. + State.SendBufferInfo = sendDspAddress; + State.SendBufferInfoBase = sendDspAddress + (ulong)Unsafe.SizeOf(); + State.ReturnBufferInfo = 0; + State.ReturnBufferInfoBase = 0; + } + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs new file mode 100644 index 00000000..32162abc --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs @@ -0,0 +1,67 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a compressor effect. + /// + public class CompressorEffect : BaseEffect + { + /// + /// The compressor parameter. + /// + public CompressorParameter Parameter; + + /// + /// The compressor state. + /// + public Memory State { get; } + + /// + /// Create a new . + /// + public CompressorEffect() + { + State = new CompressorState[1]; + } + + public override EffectType TargetEffectType => EffectType.Compressor; + + public override ulong GetWorkBuffer(int index) + { + return GetSingleBuffer(); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + // Nintendo doesn't do anything here but we still require updateErrorInfo to be initialised. + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(ref parameter)); + + UpdateParameterBase(ref parameter); + + Parameter = MemoryMarshal.Cast(parameter.SpecificData)[0]; + IsEnabled = parameter.IsEnabled; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.Status = UsageState.Enabled; + } + } +} diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs new file mode 100644 index 00000000..3f5d70bc --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs @@ -0,0 +1,93 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a delay effect. + /// + public class DelayEffect : BaseEffect + { + /// + /// The delay parameter. + /// + public DelayParameter Parameter; + + /// + /// The delay state. + /// + public Memory State { get; } + + public DelayEffect() + { + State = new DelayState[1]; + } + + public override EffectType TargetEffectType => EffectType.Delay; + + public override DspAddress GetWorkBuffer(int index) + { + return GetSingleBuffer(); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(ref parameter)); + + ref DelayParameter delayParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + if (delayParameter.IsChannelCountMaxValid()) + { + UpdateParameterBase(ref parameter); + + UsageState oldParameterStatus = Parameter.Status; + + Parameter = delayParameter; + + if (delayParameter.IsChannelCountValid()) + { + IsEnabled = parameter.IsEnabled; + + if (oldParameterStatus != UsageState.Enabled) + { + Parameter.Status = oldParameterStatus; + } + + if (BufferUnmapped || parameter.IsNew) + { + UsageState = UsageState.New; + Parameter.Status = UsageState.Invalid; + + BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize); + } + } + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.Status = UsageState.Enabled; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs new file mode 100644 index 00000000..bfb6528b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs @@ -0,0 +1,123 @@ +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Effect context. + /// + public class EffectContext + { + /// + /// Storage for . + /// + private BaseEffect[] _effects; + + /// + /// The total effect count. + /// + private uint _effectCount; + + private EffectResultState[] _resultStatesCpu; + private EffectResultState[] _resultStatesDsp; + + /// + /// Create a new . + /// + public EffectContext() + { + _effects = null; + _effectCount = 0; + } + + /// + /// Initialize the . + /// + /// The total effect count. + /// The total result state count. + public void Initialize(uint effectCount, uint resultStateCount) + { + _effectCount = effectCount; + _effects = new BaseEffect[effectCount]; + + for (int i = 0; i < _effectCount; i++) + { + _effects[i] = new BaseEffect(); + } + + _resultStatesCpu = new EffectResultState[resultStateCount]; + _resultStatesDsp = new EffectResultState[resultStateCount]; + } + + /// + /// Get the total effect count. + /// + /// The total effect count. + public uint GetCount() + { + return _effectCount; + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref BaseEffect GetEffect(int index) + { + Debug.Assert(index >= 0 && index < _effectCount); + + return ref _effects[index]; + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + /// The returned should only be used when updating the server state. + public ref EffectResultState GetState(int index) + { + Debug.Assert(index >= 0 && index < _resultStatesCpu.Length); + + return ref _resultStatesCpu[index]; + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + /// The returned should only be used in the context of processing on the . + public ref EffectResultState GetDspState(int index) + { + Debug.Assert(index >= 0 && index < _resultStatesDsp.Length); + + return ref _resultStatesDsp[index]; + } + + /// + /// Get a memory instance to a at the given . + /// + /// The index to use. + /// A memory instance to a at the given . + /// The returned should only be used in the context of processing on the . + public Memory GetDspStateMemory(int index) + { + return SpanIOHelper.GetMemory(_resultStatesDsp.AsMemory(), index, (uint)_resultStatesDsp.Length); + } + + /// + /// Update internal state during command generation. + /// + public void UpdateResultStateForCommandGeneration() + { + for (int index = 0; index < _resultStatesCpu.Length; index++) + { + _effects[index].UpdateResultState(ref _resultStatesCpu[index], ref _resultStatesDsp[index]); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs new file mode 100644 index 00000000..6e17ef3d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs @@ -0,0 +1,95 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a limiter effect. + /// + public class LimiterEffect : BaseEffect + { + /// + /// The limiter parameter. + /// + public LimiterParameter Parameter; + + /// + /// The limiter state. + /// + public Memory State { get; } + + /// + /// Create a new . + /// + public LimiterEffect() + { + State = new LimiterState[1]; + } + + public override EffectType TargetEffectType => EffectType.Limiter; + + public override ulong GetWorkBuffer(int index) + { + return GetSingleBuffer(); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(ref parameter)); + + ref LimiterParameter limiterParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + UpdateParameterBase(ref parameter); + + Parameter = limiterParameter; + + IsEnabled = parameter.IsEnabled; + + if (BufferUnmapped || parameter.IsNew) + { + UsageState = UsageState.New; + Parameter.Status = UsageState.Invalid; + + BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize); + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.Status = UsageState.Enabled; + Parameter.StatisticsReset = false; + } + + public override void InitializeResultState(ref EffectResultState state) + { + ref LimiterStatistics statistics = ref MemoryMarshal.Cast(state.SpecificData)[0]; + + statistics.Reset(); + } + + public override void UpdateResultState(ref EffectResultState destState, ref EffectResultState srcState) + { + destState = srcState; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs new file mode 100644 index 00000000..473fddb8 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs @@ -0,0 +1,92 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a 3D reverberation effect. + /// + public class Reverb3dEffect : BaseEffect + { + /// + /// The 3D reverberation parameter. + /// + public Reverb3dParameter Parameter; + + /// + /// The 3D reverberation state. + /// + public Memory State { get; } + + public Reverb3dEffect() + { + State = new Reverb3dState[1]; + } + + public override EffectType TargetEffectType => EffectType.Reverb3d; + + public override ulong GetWorkBuffer(int index) + { + return GetSingleBuffer(); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(ref parameter)); + + ref Reverb3dParameter reverbParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + if (reverbParameter.IsChannelCountMaxValid()) + { + UpdateParameterBase(ref parameter); + + UsageState oldParameterStatus = Parameter.ParameterStatus; + + Parameter = reverbParameter; + + if (reverbParameter.IsChannelCountValid()) + { + IsEnabled = parameter.IsEnabled; + + if (oldParameterStatus != UsageState.Enabled) + { + Parameter.ParameterStatus = oldParameterStatus; + } + + if (BufferUnmapped || parameter.IsNew) + { + UsageState = UsageState.New; + Parameter.ParameterStatus = UsageState.Invalid; + + BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize); + } + } + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.ParameterStatus = UsageState.Enabled; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs new file mode 100644 index 00000000..e1543fd1 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs @@ -0,0 +1,95 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Dsp.State; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// Server state for a reverberation effect. + /// + public class ReverbEffect : BaseEffect + { + /// + /// The reverberation parameter. + /// + public ReverbParameter Parameter; + + /// + /// The reverberation state. + /// + public Memory State { get; } + + /// + /// Create a new . + /// + public ReverbEffect() + { + State = new ReverbState[1]; + } + + public override EffectType TargetEffectType => EffectType.Reverb; + + public override ulong GetWorkBuffer(int index) + { + return GetSingleBuffer(); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + { + Update(out updateErrorInfo, ref parameter, mapper); + } + + public void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + Debug.Assert(IsTypeValid(ref parameter)); + + ref ReverbParameter reverbParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + updateErrorInfo = new BehaviourParameter.ErrorInfo(); + + if (reverbParameter.IsChannelCountMaxValid()) + { + UpdateParameterBase(ref parameter); + + UsageState oldParameterStatus = Parameter.Status; + + Parameter = reverbParameter; + + if (reverbParameter.IsChannelCountValid()) + { + IsEnabled = parameter.IsEnabled; + + if (oldParameterStatus != UsageState.Enabled) + { + Parameter.Status = oldParameterStatus; + } + + if (BufferUnmapped || parameter.IsNew) + { + UsageState = UsageState.New; + Parameter.Status = UsageState.Invalid; + + BufferUnmapped = !mapper.TryAttachBuffer(out updateErrorInfo, ref WorkBuffers[0], parameter.BufferBase, parameter.BufferSize); + } + } + } + } + + public override void UpdateForCommandGeneration() + { + UpdateUsageStateForCommandGeneration(); + + Parameter.Status = UsageState.Enabled; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/UsageState.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/UsageState.cs new file mode 100644 index 00000000..8648aa2c --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/UsageState.cs @@ -0,0 +1,28 @@ +namespace Ryujinx.Audio.Renderer.Server.Effect +{ + /// + /// The usage state of an effect. + /// + public enum UsageState : byte + { + /// + /// The effect is in an invalid state. + /// + Invalid, + + /// + /// The effect is new. + /// + New, + + /// + /// The effect is enabled. + /// + Enabled, + + /// + /// The effect is disabled. + /// + Disabled + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs b/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs new file mode 100644 index 00000000..4872ddb3 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs @@ -0,0 +1,40 @@ +using Ryujinx.Audio.Renderer.Dsp.Command; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// Estimate the time that a should take. + /// + /// This is used for voice dropping. + public interface ICommandProcessingTimeEstimator + { + uint Estimate(AuxiliaryBufferCommand command); + uint Estimate(BiquadFilterCommand command); + uint Estimate(ClearMixBufferCommand command); + uint Estimate(DelayCommand command); + uint Estimate(Reverb3dCommand command); + uint Estimate(ReverbCommand command); + uint Estimate(DepopPrepareCommand command); + uint Estimate(DepopForMixBuffersCommand command); + uint Estimate(MixCommand command); + uint Estimate(MixRampCommand command); + uint Estimate(MixRampGroupedCommand command); + uint Estimate(CopyMixBufferCommand command); + uint Estimate(PerformanceCommand command); + uint Estimate(VolumeCommand command); + uint Estimate(VolumeRampCommand command); + uint Estimate(PcmInt16DataSourceCommandVersion1 command); + uint Estimate(PcmFloatDataSourceCommandVersion1 command); + uint Estimate(AdpcmDataSourceCommandVersion1 command); + uint Estimate(DataSourceVersion2Command command); + uint Estimate(CircularBufferSinkCommand command); + uint Estimate(DeviceSinkCommand command); + uint Estimate(DownMixSurroundToStereoCommand command); + uint Estimate(UpsampleCommand command); + uint Estimate(LimiterCommandVersion1 command); + uint Estimate(LimiterCommandVersion2 command); + uint Estimate(GroupedBiquadFilterCommand command); + uint Estimate(CaptureBufferCommand command); + uint Estimate(CompressorCommand command); + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/MemoryPool/AddressInfo.cs b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/AddressInfo.cs new file mode 100644 index 00000000..5fd6b2b9 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/AddressInfo.cs @@ -0,0 +1,133 @@ +using System; +using System.Runtime.InteropServices; +using CpuAddress = System.UInt64; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.MemoryPool +{ + /// + /// Represents the information of a region shared between the CPU and DSP. + /// + public struct AddressInfo + { + /// + /// The target CPU address of the region. + /// + public CpuAddress CpuAddress; + + /// + /// The size of the region. + /// + public ulong Size; + + private unsafe MemoryPoolState* _memoryPools; + + /// + /// The forced DSP address of the region. + /// + public DspAddress ForceMappedDspAddress; + + private unsafe ref MemoryPoolState MemoryPoolState => ref *_memoryPools; + + public unsafe bool HasMemoryPoolState => (IntPtr)_memoryPools != IntPtr.Zero; + + /// + /// Create an new empty . + /// + /// A new empty . + public static AddressInfo Create() + { + return Create(0, 0); + } + + /// + /// Create a new . + /// + /// The target of the region. + /// The target size of the region. + /// A new . + public static AddressInfo Create(CpuAddress cpuAddress, ulong size) + { + unsafe + { + return new AddressInfo + { + CpuAddress = cpuAddress, + _memoryPools = MemoryPoolState.Null, + Size = size, + ForceMappedDspAddress = 0 + }; + } + } + + /// + /// Setup the CPU address and size of the . + /// + /// The target of the region. + /// The size of the region. + public void Setup(CpuAddress cpuAddress, ulong size) + { + CpuAddress = cpuAddress; + Size = size; + ForceMappedDspAddress = 0; + + unsafe + { + _memoryPools = MemoryPoolState.Null; + } + } + + /// + /// Set the associated. + /// + /// The associated. + public void SetupMemoryPool(Span memoryPoolState) + { + unsafe + { + fixed (MemoryPoolState* ptr = &MemoryMarshal.GetReference(memoryPoolState)) + { + SetupMemoryPool(ptr); + } + } + } + + /// + /// Set the associated. + /// + /// The associated. + public unsafe void SetupMemoryPool(MemoryPoolState* memoryPoolState) + { + _memoryPools = memoryPoolState; + } + + /// + /// Check if the is mapped. + /// + /// Returns true if the is mapped. + public bool HasMappedMemoryPool() + { + return HasMemoryPoolState && MemoryPoolState.IsMapped(); + } + + /// + /// Get the DSP address associated to the . + /// + /// If true, mark the as used. + /// Returns the DSP address associated to the . + public DspAddress GetReference(bool markUsed) + { + if (!HasMappedMemoryPool()) + { + return ForceMappedDspAddress; + } + + if (markUsed) + { + MemoryPoolState.IsUsed = true; + } + + return MemoryPoolState.Translate(CpuAddress, Size); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/MemoryPool/MemoryPoolState.cs b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/MemoryPoolState.cs new file mode 100644 index 00000000..69466bab --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/MemoryPoolState.cs @@ -0,0 +1,130 @@ +using System; +using System.Runtime.InteropServices; +using CpuAddress = System.UInt64; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.MemoryPool +{ + /// + /// Server state for a memory pool. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = Alignment)] + public struct MemoryPoolState + { + public const int Alignment = 0x10; + + /// + /// The location of the . + /// + public enum LocationType : uint + { + /// + /// located on the CPU side for user use. + /// + Cpu, + + /// + /// located on the DSP side for system use. + /// + Dsp + } + + /// + /// The CPU address associated to the . + /// + public CpuAddress CpuAddress; + + /// + /// The DSP address associated to the . + /// + public DspAddress DspAddress; + + /// + /// The size associated to the . + /// + public ulong Size; + + /// + /// The associated to the . + /// + public LocationType Location; + + /// + /// Set to true if the is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + public static unsafe MemoryPoolState* Null => (MemoryPoolState*)IntPtr.Zero.ToPointer(); + + /// + /// Create a new with the given . + /// + /// The location type to use. + /// A new with the given . + public static MemoryPoolState Create(LocationType location) + { + return new MemoryPoolState + { + CpuAddress = 0, + DspAddress = 0, + Size = 0, + Location = location + }; + } + + /// + /// Set the and size of the . + /// + /// The . + /// The size. + public void SetCpuAddress(CpuAddress cpuAddress, ulong size) + { + CpuAddress = cpuAddress; + Size = size; + } + + /// + /// Check if the given and size is contains in the . + /// + /// The . + /// The size. + /// True if the is contained inside the . + public bool Contains(CpuAddress targetCpuAddress, ulong size) + { + if (CpuAddress <= targetCpuAddress && size + targetCpuAddress <= Size + CpuAddress) + { + return true; + } + + return false; + } + + /// + /// Translate the given CPU address to a DSP address. + /// + /// The . + /// The size. + /// the target DSP address. + public DspAddress Translate(CpuAddress targetCpuAddress, ulong size) + { + if (Contains(targetCpuAddress, size) && IsMapped()) + { + ulong offset = targetCpuAddress - CpuAddress; + + return DspAddress + offset; + } + + return 0; + } + + /// + /// Is the mapped on the DSP? + /// + /// Returns true if the is mapped on the DSP. + public bool IsMapped() + { + return DspAddress != 0; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs new file mode 100644 index 00000000..6c79da15 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs @@ -0,0 +1,366 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common.Logging; +using System; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +using CpuAddress = System.UInt64; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Server.MemoryPool +{ + /// + /// Memory pool mapping helper. + /// + public class PoolMapper + { + const uint CurrentProcessPseudoHandle = 0xFFFF8001; + + /// + /// The result of . + /// + public enum UpdateResult : uint + { + /// + /// No error reported. + /// + Success = 0, + + /// + /// The user parameters were invalid. + /// + InvalidParameter = 1, + + /// + /// mapping failed. + /// + MapError = 2, + + /// + /// unmapping failed. + /// + UnmapError = 3 + } + + /// + /// The handle of the process owning the CPU memory manipulated. + /// + private uint _processHandle; + + /// + /// The that will be manipulated. + /// + private Memory _memoryPools; + + /// + /// If set to true, this will try to force map memory pool even if their state are considered invalid. + /// + private bool _isForceMapEnabled; + + /// + /// Create a new used for system mapping. + /// + /// The handle of the process owning the CPU memory manipulated. + /// If set to true, this will try to force map memory pool even if their state are considered invalid. + public PoolMapper(uint processHandle, bool isForceMapEnabled) + { + _processHandle = processHandle; + _isForceMapEnabled = isForceMapEnabled; + _memoryPools = Memory.Empty; + } + + /// + /// Create a new used for user mapping. + /// + /// The handle of the process owning the CPU memory manipulated. + /// The user memory pools. + /// If set to true, this will try to force map memory pool even if their state are considered invalid. + public PoolMapper(uint processHandle, Memory memoryPool, bool isForceMapEnabled) + { + _processHandle = processHandle; + _memoryPools = memoryPool; + _isForceMapEnabled = isForceMapEnabled; + } + + /// + /// Initialize the for system use. + /// + /// The for system use. + /// The to assign. + /// The size to assign. + /// Returns true if mapping on the succeeded. + public bool InitializeSystemPool(ref MemoryPoolState memoryPool, CpuAddress cpuAddress, ulong size) + { + if (memoryPool.Location != MemoryPoolState.LocationType.Dsp) + { + return false; + } + + return InitializePool(ref memoryPool, cpuAddress, size); + } + + /// + /// Initialize the . + /// + /// The . + /// The to assign. + /// The size to assign. + /// Returns true if mapping on the succeeded. + public bool InitializePool(ref MemoryPoolState memoryPool, CpuAddress cpuAddress, ulong size) + { + memoryPool.SetCpuAddress(cpuAddress, size); + + return Map(ref memoryPool) != 0; + } + + /// + /// Get the process handle associated to the . + /// + /// The . + /// Returns the process handle associated to the . + public uint GetProcessHandle(ref MemoryPoolState memoryPool) + { + if (memoryPool.Location == MemoryPoolState.LocationType.Cpu) + { + return CurrentProcessPseudoHandle; + } + else if (memoryPool.Location == MemoryPoolState.LocationType.Dsp) + { + return _processHandle; + } + + return 0; + } + + /// + /// Map the on the . + /// + /// The to map. + /// Returns the DSP address mapped. + public DspAddress Map(ref MemoryPoolState memoryPool) + { + DspAddress result = AudioProcessorMemoryManager.Map(GetProcessHandle(ref memoryPool), memoryPool.CpuAddress, memoryPool.Size); + + if (result != 0) + { + memoryPool.DspAddress = result; + } + + return result; + } + + /// + /// Unmap the from the . + /// + /// The to unmap. + /// Returns true if unmapped. + public bool Unmap(ref MemoryPoolState memoryPool) + { + if (memoryPool.IsUsed) + { + return false; + } + + AudioProcessorMemoryManager.Unmap(GetProcessHandle(ref memoryPool), memoryPool.CpuAddress, memoryPool.Size); + + memoryPool.SetCpuAddress(0, 0); + memoryPool.DspAddress = 0; + + return true; + } + + /// + /// Find a associated to the region given. + /// + /// The region . + /// The region size. + /// Returns the found or if not found. + private Span FindMemoryPool(CpuAddress cpuAddress, ulong size) + { + if (!_memoryPools.IsEmpty && _memoryPools.Length > 0) + { + for (int i = 0; i < _memoryPools.Length; i++) + { + if (_memoryPools.Span[i].Contains(cpuAddress, size)) + { + return _memoryPools.Span.Slice(i, 1); + } + } + } + + return Span.Empty; + } + + /// + /// Force unmap the given . + /// + /// The to force unmap + public void ForceUnmap(ref AddressInfo addressInfo) + { + if (_isForceMapEnabled) + { + Span memoryPool = FindMemoryPool(addressInfo.CpuAddress, addressInfo.Size); + + if (!memoryPool.IsEmpty) + { + AudioProcessorMemoryManager.Unmap(_processHandle, memoryPool[0].CpuAddress, memoryPool[0].Size); + + return; + } + + AudioProcessorMemoryManager.Unmap(_processHandle, addressInfo.CpuAddress, 0); + } + } + + /// + /// Try to attach the given region to the . + /// + /// The error information if an error was generated. + /// The to attach the region to. + /// The region . + /// The region size. + /// Returns true if mapping was performed. + public bool TryAttachBuffer(out ErrorInfo errorInfo, ref AddressInfo addressInfo, CpuAddress cpuAddress, ulong size) + { + errorInfo = new ErrorInfo(); + + addressInfo.Setup(cpuAddress, size); + + if (AssignDspAddress(ref addressInfo)) + { + errorInfo.ErrorCode = 0x0; + errorInfo.ExtraErrorInfo = 0x0; + + return true; + } + else + { + errorInfo.ErrorCode = ResultCode.InvalidAddressInfo; + errorInfo.ExtraErrorInfo = addressInfo.CpuAddress; + + return _isForceMapEnabled; + } + } + + /// + /// Update a using user parameters. + /// + /// The to update. + /// Input user parameter. + /// Output user parameter. + /// Returns the of the operations performed. + public UpdateResult Update(ref MemoryPoolState memoryPool, ref MemoryPoolInParameter inParameter, ref MemoryPoolOutStatus outStatus) + { + MemoryPoolUserState inputState = inParameter.State; + + MemoryPoolUserState outputState; + + const uint pageSize = 0x1000; + + if (inputState != MemoryPoolUserState.RequestAttach && inputState != MemoryPoolUserState.RequestDetach) + { + return UpdateResult.Success; + } + + if (inParameter.CpuAddress == 0 || (inParameter.CpuAddress % pageSize) != 0) + { + return UpdateResult.InvalidParameter; + } + + if (inParameter.Size == 0 || (inParameter.Size % pageSize) != 0) + { + return UpdateResult.InvalidParameter; + } + + if (inputState == MemoryPoolUserState.RequestAttach) + { + bool initializeSuccess = InitializePool(ref memoryPool, inParameter.CpuAddress, inParameter.Size); + + if (!initializeSuccess) + { + memoryPool.SetCpuAddress(0, 0); + + Logger.Error?.Print(LogClass.AudioRenderer, $"Map of memory pool (address: 0x{inParameter.CpuAddress:x}, size 0x{inParameter.Size:x}) failed!"); + return UpdateResult.MapError; + } + + outputState = MemoryPoolUserState.Attached; + } + else + { + if (memoryPool.CpuAddress != inParameter.CpuAddress || memoryPool.Size != inParameter.Size) + { + return UpdateResult.InvalidParameter; + } + + if (!Unmap(ref memoryPool)) + { + Logger.Error?.Print(LogClass.AudioRenderer, $"Unmap of memory pool (address: 0x{memoryPool.CpuAddress:x}, size 0x{memoryPool.Size:x}) failed!"); + return UpdateResult.UnmapError; + } + + outputState = MemoryPoolUserState.Detached; + } + + outStatus.State = outputState; + + return UpdateResult.Success; + } + + /// + /// Map the to the . + /// + /// The to map. + /// Returns true if mapping was performed. + private bool AssignDspAddress(ref AddressInfo addressInfo) + { + if (addressInfo.CpuAddress == 0) + { + return false; + } + + if (_memoryPools.Length > 0) + { + Span memoryPool = FindMemoryPool(addressInfo.CpuAddress, addressInfo.Size); + + if (!memoryPool.IsEmpty) + { + addressInfo.SetupMemoryPool(memoryPool); + + return true; + } + } + + if (_isForceMapEnabled) + { + DspAddress dspAddress = AudioProcessorMemoryManager.Map(_processHandle, addressInfo.CpuAddress, addressInfo.Size); + + addressInfo.ForceMappedDspAddress = dspAddress; + + AudioProcessorMemoryManager.Map(_processHandle, addressInfo.CpuAddress, addressInfo.Size); + } + else + { + unsafe + { + addressInfo.SetupMemoryPool(MemoryPoolState.Null); + } + } + + return false; + } + + /// + /// Remove the usage flag from all the . + /// + /// The to reset. + public static void ClearUsageState(Memory memoryPool) + { + foreach (ref MemoryPoolState info in memoryPool.Span) + { + info.IsUsed = false; + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs b/src/Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs new file mode 100644 index 00000000..cda6f737 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs @@ -0,0 +1,259 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Server.Splitter; +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server.Mix +{ + /// + /// Mix context. + /// + public class MixContext + { + /// + /// The total mix count. + /// + private uint _mixesCount; + + /// + /// Storage for . + /// + private Memory _mixes; + + /// + /// Storage of the sorted indices to . + /// + private Memory _sortedMixes; + + /// + /// Graph state. + /// + public NodeStates NodeStates { get; } + + /// + /// The instance of the adjacent matrix. + /// + public EdgeMatrix EdgeMatrix { get; } + + /// + /// Create a new instance of . + /// + public MixContext() + { + NodeStates = new NodeStates(); + EdgeMatrix = new EdgeMatrix(); + } + + /// + /// Initialize the . + /// + /// The storage for sorted indices. + /// The storage of . + /// The storage used for the . + /// The storage used for the . + public void Initialize(Memory sortedMixes, Memory mixes, Memory nodeStatesWorkBuffer, Memory edgeMatrixWorkBuffer) + { + _mixesCount = (uint)mixes.Length; + _mixes = mixes; + _sortedMixes = sortedMixes; + + if (!nodeStatesWorkBuffer.IsEmpty && !edgeMatrixWorkBuffer.IsEmpty) + { + NodeStates.Initialize(nodeStatesWorkBuffer, mixes.Length); + EdgeMatrix.Initialize(edgeMatrixWorkBuffer, mixes.Length); + } + + int sortedId = 0; + for (int i = 0; i < _mixes.Length; i++) + { + SetSortedState(sortedId++, i); + } + } + + /// + /// Associate the given to a given . + /// + /// The sorted id. + /// The index to associate. + private void SetSortedState(int id, int targetIndex) + { + _sortedMixes.Span[id] = targetIndex; + } + + /// + /// Get a reference to the final . + /// + /// A reference to the final . + public ref MixState GetFinalState() + { + return ref GetState(Constants.FinalMixId); + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref MixState GetState(int id) + { + return ref SpanIOHelper.GetFromMemory(_mixes, id, _mixesCount); + } + + /// + /// Get a reference to a at the given of the sorted mix info. + /// + /// The index to use. + /// A reference to a at the given . + public ref MixState GetSortedState(int id) + { + Debug.Assert(id >= 0 && id < _mixesCount); + + return ref GetState(_sortedMixes.Span[id]); + } + + /// + /// Get the total mix count. + /// + /// The total mix count. + public uint GetCount() + { + return _mixesCount; + } + + /// + /// Update the internal distance from the final mix value of every . + /// + private void UpdateDistancesFromFinalMix() + { + foreach (ref MixState mix in _mixes.Span) + { + mix.ClearDistanceFromFinalMix(); + } + + for (int i = 0; i < GetCount(); i++) + { + ref MixState mix = ref GetState(i); + + SetSortedState(i, i); + + if (mix.IsUsed) + { + uint distance; + + if (mix.MixId != Constants.FinalMixId) + { + int mixId = mix.MixId; + + for (distance = 0; distance < GetCount(); distance++) + { + if (mixId == Constants.UnusedMixId) + { + distance = MixState.InvalidDistanceFromFinalMix; + break; + } + + ref MixState distanceMix = ref GetState(mixId); + + if (distanceMix.DistanceFromFinalMix != MixState.InvalidDistanceFromFinalMix) + { + distance = distanceMix.DistanceFromFinalMix + 1; + break; + } + + mixId = distanceMix.DestinationMixId; + + if (mixId == Constants.FinalMixId) + { + break; + } + } + + if (distance > GetCount()) + { + distance = MixState.InvalidDistanceFromFinalMix; + } + } + else + { + distance = MixState.InvalidDistanceFromFinalMix; + } + + mix.DistanceFromFinalMix = distance; + } + } + } + + /// + /// Update the internal mix buffer offset of all . + /// + private void UpdateMixBufferOffset() + { + uint offset = 0; + + foreach (ref MixState mix in _mixes.Span) + { + mix.BufferOffset = offset; + + offset += mix.BufferCount; + } + } + + /// + /// Sort the mixes using distance from the final mix. + /// + public void Sort() + { + UpdateDistancesFromFinalMix(); + + int[] sortedMixesTemp = _sortedMixes.Slice(0, (int)GetCount()).ToArray(); + + Array.Sort(sortedMixesTemp, (a, b) => + { + ref MixState stateA = ref GetState(a); + ref MixState stateB = ref GetState(b); + + return stateB.DistanceFromFinalMix.CompareTo(stateA.DistanceFromFinalMix); + }); + + sortedMixesTemp.AsSpan().CopyTo(_sortedMixes.Span); + + UpdateMixBufferOffset(); + } + + /// + /// Sort the mixes and splitters using an adjacency matrix. + /// + /// The used. + /// Return true, if no errors in the graph were detected. + public bool Sort(SplitterContext splitterContext) + { + if (splitterContext.UsingSplitter()) + { + bool isValid = NodeStates.Sort(EdgeMatrix); + + if (isValid) + { + ReadOnlySpan sortedMixesIndex = NodeStates.GetTsortResult(); + + int id = 0; + + for (int i = sortedMixesIndex.Length - 1; i >= 0; i--) + { + SetSortedState(id++, sortedMixesIndex[i]); + } + + UpdateMixBufferOffset(); + } + + return isValid; + } + else + { + UpdateMixBufferOffset(); + + return true; + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs b/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs new file mode 100644 index 00000000..146e6781 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs @@ -0,0 +1,313 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Audio.Renderer.Server.Splitter; +using Ryujinx.Common.Utilities; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using static Ryujinx.Audio.Constants; + +namespace Ryujinx.Audio.Renderer.Server.Mix +{ + /// + /// Server state for a mix. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x940, Pack = Alignment)] + public struct MixState + { + public const uint InvalidDistanceFromFinalMix = 0x80000000; + + public const int Alignment = 0x10; + + /// + /// Base volume of the mix. + /// + public float Volume; + + /// + /// Target sample rate of the mix. + /// + public uint SampleRate; + + /// + /// Target buffer count. + /// + public uint BufferCount; + + /// + /// Set to true if in use. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// The id of the mix. + /// + public int MixId; + + /// + /// The mix node id. + /// + public int NodeId; + + /// + /// the buffer offset to use for command generation. + /// + public uint BufferOffset; + + /// + /// The distance of the mix from the final mix. + /// + public uint DistanceFromFinalMix; + + /// + /// The effect processing order storage. + /// + private IntPtr _effectProcessingOrderArrayPointer; + + /// + /// The max element count that can be found in the effect processing order storage. + /// + public uint EffectProcessingOrderArrayMaxCount; + + /// + /// The mix to output the result of this mix. + /// + public int DestinationMixId; + + /// + /// Mix buffer volumes storage. + /// + private MixVolumeArray _mixVolumeArray; + + /// + /// The splitter to output the result of this mix. + /// + public uint DestinationSplitterId; + + /// + /// If set to true, the long size pre-delay is supported on the reverb command. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsLongSizePreDelaySupported; + + [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)] + private struct MixVolumeArray + { + private const int Size = 4 * MixBufferCountMax * MixBufferCountMax; + } + + /// + /// Mix buffer volumes. + /// + /// Used when no splitter id is specified. + public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mixVolumeArray); + + /// + /// Get the volume for a given connection destination. + /// + /// The source node index. + /// The destination node index + /// The volume for the given connection destination. + public float GetMixBufferVolume(int sourceIndex, int destinationIndex) + { + return MixBufferVolume[sourceIndex * MixBufferCountMax + destinationIndex]; + } + + /// + /// The array used to order effects associated to this mix. + /// + public Span EffectProcessingOrderArray + { + get + { + if (_effectProcessingOrderArrayPointer == IntPtr.Zero) + { + return Span.Empty; + } + + unsafe + { + return new Span((void*)_effectProcessingOrderArrayPointer, (int)EffectProcessingOrderArrayMaxCount); + } + } + } + + /// + /// Create a new + /// + /// + /// + public MixState(Memory effectProcessingOrderArray, ref BehaviourContext behaviourContext) : this() + { + MixId = UnusedMixId; + + DistanceFromFinalMix = InvalidDistanceFromFinalMix; + + DestinationMixId = UnusedMixId; + + DestinationSplitterId = UnusedSplitterId; + + unsafe + { + // SAFETY: safe as effectProcessingOrderArray comes from the work buffer memory that is pinned. + _effectProcessingOrderArrayPointer = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetReference(effectProcessingOrderArray.Span)); + } + + EffectProcessingOrderArrayMaxCount = (uint)effectProcessingOrderArray.Length; + + IsLongSizePreDelaySupported = behaviourContext.IsLongSizePreDelaySupported(); + + ClearEffectProcessingOrder(); + } + + /// + /// Clear the value to its default state. + /// + public void ClearDistanceFromFinalMix() + { + DistanceFromFinalMix = InvalidDistanceFromFinalMix; + } + + /// + /// Clear the to its default state. + /// + public void ClearEffectProcessingOrder() + { + EffectProcessingOrderArray.Fill(-1); + } + + /// + /// Return true if the mix has any destinations. + /// + /// True if the mix has any destinations. + public bool HasAnyDestination() + { + return DestinationMixId != UnusedMixId || DestinationSplitterId != UnusedSplitterId; + } + + /// + /// Update the mix connection on the adjacency matrix. + /// + /// The adjacency matrix. + /// The input parameter of the mix. + /// The splitter context. + /// Return true, new connections were done on the adjacency matrix. + private bool UpdateConnection(EdgeMatrix edgeMatrix, ref MixParameter parameter, ref SplitterContext splitterContext) + { + bool hasNewConnections; + + if (DestinationSplitterId == UnusedSplitterId) + { + hasNewConnections = false; + } + else + { + ref SplitterState splitter = ref splitterContext.GetState((int)DestinationSplitterId); + + hasNewConnections = splitter.HasNewConnection; + } + + if (DestinationMixId == parameter.DestinationMixId && DestinationSplitterId == parameter.DestinationSplitterId && !hasNewConnections) + { + return false; + } + + edgeMatrix.RemoveEdges(MixId); + + if (parameter.DestinationMixId == UnusedMixId) + { + if (parameter.DestinationSplitterId != UnusedSplitterId) + { + ref SplitterState splitter = ref splitterContext.GetState((int)parameter.DestinationSplitterId); + + for (int i = 0; i < splitter.DestinationCount; i++) + { + Span destination = splitter.GetData(i); + + if (!destination.IsEmpty) + { + int destinationMixId = destination[0].DestinationId; + + if (destinationMixId != UnusedMixId) + { + edgeMatrix.Connect(MixId, destinationMixId); + } + } + } + } + } + else + { + edgeMatrix.Connect(MixId, parameter.DestinationMixId); + } + + DestinationMixId = parameter.DestinationMixId; + DestinationSplitterId = parameter.DestinationSplitterId; + + return true; + } + + /// + /// Update the mix from user information. + /// + /// The adjacency matrix. + /// The input parameter of the mix. + /// The effect context. + /// The splitter context. + /// The behaviour context. + /// Return true if the mix was changed. + public bool Update(EdgeMatrix edgeMatrix, ref MixParameter parameter, EffectContext effectContext, SplitterContext splitterContext, BehaviourContext behaviourContext) + { + bool isDirty; + + Volume = parameter.Volume; + SampleRate = parameter.SampleRate; + BufferCount = parameter.BufferCount; + IsUsed = parameter.IsUsed; + MixId = parameter.MixId; + NodeId = parameter.NodeId; + parameter.MixBufferVolume.CopyTo(MixBufferVolume); + + if (behaviourContext.IsSplitterSupported()) + { + isDirty = UpdateConnection(edgeMatrix, ref parameter, ref splitterContext); + } + else + { + isDirty = DestinationMixId != parameter.DestinationMixId; + + if (DestinationMixId != parameter.DestinationMixId) + { + DestinationMixId = parameter.DestinationMixId; + } + + DestinationSplitterId = UnusedSplitterId; + } + + ClearEffectProcessingOrder(); + + for (int i = 0; i < effectContext.GetCount(); i++) + { + ref BaseEffect effect = ref effectContext.GetEffect(i); + + if (effect.MixId == MixId) + { + Debug.Assert(effect.ProcessingOrder <= EffectProcessingOrderArrayMaxCount); + + if (effect.ProcessingOrder > EffectProcessingOrderArrayMaxCount) + { + return isDirty; + } + + EffectProcessingOrderArray[(int)effect.ProcessingOrder] = i; + } + } + + return isDirty; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceDetailEntry.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceDetailEntry.cs new file mode 100644 index 00000000..dbe59cb0 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceDetailEntry.cs @@ -0,0 +1,52 @@ +using Ryujinx.Audio.Renderer.Common; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Represents a detailed entry in a performance frame. + /// + public interface IPerformanceDetailEntry + { + /// + /// Get the start time of this entry event (in microseconds). + /// + /// The start time of this entry event (in microseconds). + int GetStartTime(); + + /// + /// Get the start time offset in this structure. + /// + /// The start time offset in this structure. + int GetStartTimeOffset(); + + /// + /// Get the processing time of this entry event (in microseconds). + /// + /// The processing time of this entry event (in microseconds). + int GetProcessingTime(); + + /// + /// Get the processing time offset in this structure. + /// + /// The processing time offset in this structure. + int GetProcessingTimeOffset(); + + /// + /// Set the of this entry. + /// + /// The node id of this entry. + void SetNodeId(int nodeId); + + /// + /// Set the of this entry. + /// + /// The type to use. + void SetEntryType(PerformanceEntryType type); + + /// + /// Set the of this entry. + /// + /// The type to use. + void SetDetailType(PerformanceDetailType detailType); + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceEntry.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceEntry.cs new file mode 100644 index 00000000..9888a4cc --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceEntry.cs @@ -0,0 +1,46 @@ +using Ryujinx.Audio.Renderer.Common; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Represents an entry in a performance frame. + /// + public interface IPerformanceEntry + { + /// + /// Get the start time of this entry event (in microseconds). + /// + /// The start time of this entry event (in microseconds). + int GetStartTime(); + + /// + /// Get the start time offset in this structure. + /// + /// The start time offset in this structure. + int GetStartTimeOffset(); + + /// + /// Get the processing time of this entry event (in microseconds). + /// + /// The processing time of this entry event (in microseconds). + int GetProcessingTime(); + + /// + /// Get the processing time offset in this structure. + /// + /// The processing time offset in this structure. + int GetProcessingTimeOffset(); + + /// + /// Set the of this entry. + /// + /// The node id of this entry. + void SetNodeId(int nodeId); + + /// + /// Set the of this entry. + /// + /// The type to use. + void SetEntryType(PerformanceEntryType type); + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceHeader.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceHeader.cs new file mode 100644 index 00000000..21876b4b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceHeader.cs @@ -0,0 +1,80 @@ +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// The header of a performance frame. + /// + public interface IPerformanceHeader + { + /// + /// Get the entry count offset in this structure. + /// + /// The entry count offset in this structure. + int GetEntryCountOffset(); + + /// + /// Set the DSP running behind flag. + /// + /// The flag. + void SetDspRunningBehind(bool isRunningBehind); + + /// + /// Set the count of voices that were dropped. + /// + /// The count of voices that were dropped. + void SetVoiceDropCount(uint voiceCount); + + /// + /// Set the start ticks of the . (before sending commands) + /// + /// The start ticks of the . (before sending commands) + void SetStartRenderingTicks(ulong startTicks); + + /// + /// Set the header magic. + /// + /// The header magic. + void SetMagic(uint magic); + + /// + /// Set the offset of the next performance header. + /// + /// The offset of the next performance header. + void SetNextOffset(int nextOffset); + + /// + /// Set the total time taken by all the commands profiled. + /// + /// The total time taken by all the commands profiled. + void SetTotalProcessingTime(int totalProcessingTime); + + /// + /// Set the index of this performance frame. + /// + /// The index of this performance frame. + void SetIndex(uint index); + + /// + /// Get the total count of entries in this frame. + /// + /// The total count of entries in this frame. + int GetEntryCount(); + + /// + /// Get the total count of detailed entries in this frame. + /// + /// The total count of detailed entries in this frame. + int GetEntryDetailCount(); + + /// + /// Set the total count of entries in this frame. + /// + /// The total count of entries in this frame. + void SetEntryCount(int entryCount); + + /// + /// Set the total count of detailed entries in this frame. + /// + /// The total count of detailed entries in this frame. + void SetEntryDetailCount(int entryDetailCount); + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion1.cs new file mode 100644 index 00000000..22704c0d --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion1.cs @@ -0,0 +1,72 @@ +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 1. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] + public struct PerformanceDetailVersion1 : IPerformanceDetailEntry + { + /// + /// The node id associated to this detailed entry. + /// + public int NodeId; + + /// + /// The start time (in microseconds) associated to this detailed entry. + /// + public int StartTime; + + /// + /// The processing time (in microseconds) associated to this detailed entry. + /// + public int ProcessingTime; + + /// + /// The detailed entry type associated to this detailed entry. + /// + public PerformanceDetailType DetailType; + + /// + /// The entry type associated to this detailed entry. + /// + public PerformanceEntryType EntryType; + + public int GetProcessingTime() + { + return ProcessingTime; + } + + public int GetProcessingTimeOffset() + { + return 8; + } + + public int GetStartTime() + { + return StartTime; + } + + public int GetStartTimeOffset() + { + return 4; + } + + public void SetDetailType(PerformanceDetailType detailType) + { + DetailType = detailType; + } + + public void SetEntryType(PerformanceEntryType type) + { + EntryType = type; + } + + public void SetNodeId(int nodeId) + { + NodeId = nodeId; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion2.cs new file mode 100644 index 00000000..05ecda9b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion2.cs @@ -0,0 +1,72 @@ +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 2. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)] + public struct PerformanceDetailVersion2 : IPerformanceDetailEntry + { + /// + /// The node id associated to this detailed entry. + /// + public int NodeId; + + /// + /// The start time (in microseconds) associated to this detailed entry. + /// + public int StartTime; + + /// + /// The processing time (in microseconds) associated to this detailed entry. + /// + public int ProcessingTime; + + /// + /// The detailed entry type associated to this detailed entry. + /// + public PerformanceDetailType DetailType; + + /// + /// The entry type associated to this detailed entry. + /// + public PerformanceEntryType EntryType; + + public int GetProcessingTime() + { + return ProcessingTime; + } + + public int GetProcessingTimeOffset() + { + return 8; + } + + public int GetStartTime() + { + return StartTime; + } + + public int GetStartTimeOffset() + { + return 4; + } + + public void SetDetailType(PerformanceDetailType detailType) + { + DetailType = detailType; + } + + public void SetEntryType(PerformanceEntryType type) + { + EntryType = type; + } + + public void SetNodeId(int nodeId) + { + NodeId = nodeId; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryAddresses.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryAddresses.cs new file mode 100644 index 00000000..1b8d8668 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryAddresses.cs @@ -0,0 +1,56 @@ +using System; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Information used by the performance command to store informations in the performance entry. + /// + public class PerformanceEntryAddresses + { + /// + /// The memory storing the performance entry. + /// + public Memory BaseMemory; + + /// + /// The offset to the start time field. + /// + public uint StartTimeOffset; + + /// + /// The offset to the entry count field. + /// + public uint EntryCountOffset; + + /// + /// The offset to the processing time field. + /// + public uint ProcessingTimeOffset; + + /// + /// Increment the entry count. + /// + public void IncrementEntryCount() + { + BaseMemory.Span[(int)EntryCountOffset / 4]++; + } + + /// + /// Set the start time in the entry. + /// + /// The start time in nanoseconds. + public void SetStartTime(ulong startTimeNano) + { + BaseMemory.Span[(int)StartTimeOffset / 4] = (int)(startTimeNano / 1000); + } + + /// + /// Set the processing time in the entry. + /// + /// The end time in nanoseconds. + public void SetProcessingTime(ulong endTimeNano) + { + BaseMemory.Span[(int)ProcessingTimeOffset / 4] = (int)(endTimeNano / 1000) - BaseMemory.Span[(int)StartTimeOffset / 4]; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion1.cs new file mode 100644 index 00000000..fa2d3216 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion1.cs @@ -0,0 +1,62 @@ +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 1. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] + public struct PerformanceEntryVersion1 : IPerformanceEntry + { + /// + /// The node id associated to this entry. + /// + public int NodeId; + + /// + /// The start time (in microseconds) associated to this entry. + /// + public int StartTime; + + /// + /// The processing time (in microseconds) associated to this entry. + /// + public int ProcessingTime; + + /// + /// The entry type associated to this entry. + /// + public PerformanceEntryType EntryType; + + public int GetProcessingTime() + { + return ProcessingTime; + } + + public int GetProcessingTimeOffset() + { + return 8; + } + + public int GetStartTime() + { + return StartTime; + } + + public int GetStartTimeOffset() + { + return 4; + } + + public void SetEntryType(PerformanceEntryType type) + { + EntryType = type; + } + + public void SetNodeId(int nodeId) + { + NodeId = nodeId; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion2.cs new file mode 100644 index 00000000..49d4b3ce --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion2.cs @@ -0,0 +1,62 @@ +using Ryujinx.Audio.Renderer.Common; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 2. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)] + public struct PerformanceEntryVersion2 : IPerformanceEntry + { + /// + /// The node id associated to this entry. + /// + public int NodeId; + + /// + /// The start time (in microseconds) associated to this entry. + /// + public int StartTime; + + /// + /// The processing time (in microseconds) associated to this entry. + /// + public int ProcessingTime; + + /// + /// The entry type associated to this entry. + /// + public PerformanceEntryType EntryType; + + public int GetProcessingTime() + { + return ProcessingTime; + } + + public int GetProcessingTimeOffset() + { + return 8; + } + + public int GetStartTime() + { + return StartTime; + } + + public int GetStartTimeOffset() + { + return 4; + } + + public void SetEntryType(PerformanceEntryType type) + { + EntryType = type; + } + + public void SetNodeId(int nodeId) + { + NodeId = nodeId; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs new file mode 100644 index 00000000..5fe6bff0 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs @@ -0,0 +1,101 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 1. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)] + public struct PerformanceFrameHeaderVersion1 : IPerformanceHeader + { + /// + /// The magic of the performance header. + /// + public uint Magic; + + /// + /// The total count of entries in this frame. + /// + public int EntryCount; + + /// + /// The total count of detailed entries in this frame. + /// + public int EntryDetailCount; + + /// + /// The offset of the next performance header. + /// + public int NextOffset; + + /// + /// The total time taken by all the commands profiled. + /// + public int TotalProcessingTime; + + /// + /// The count of voices that were dropped. + /// + public uint VoiceDropCount; + + public int GetEntryCount() + { + return EntryCount; + } + + public int GetEntryCountOffset() + { + return 4; + } + + public int GetEntryDetailCount() + { + return EntryDetailCount; + } + + public void SetDspRunningBehind(bool isRunningBehind) + { + // NOTE: Not present in version 1 + } + + public void SetEntryCount(int entryCount) + { + EntryCount = entryCount; + } + + public void SetEntryDetailCount(int entryDetailCount) + { + EntryDetailCount = entryDetailCount; + } + + public void SetIndex(uint index) + { + // NOTE: Not present in version 1 + } + + public void SetMagic(uint magic) + { + Magic = magic; + } + + public void SetNextOffset(int nextOffset) + { + NextOffset = nextOffset; + } + + public void SetStartRenderingTicks(ulong startTicks) + { + // NOTE: not present in version 1 + } + + public void SetTotalProcessingTime(int totalProcessingTime) + { + TotalProcessingTime = totalProcessingTime; + } + + public void SetVoiceDropCount(uint voiceCount) + { + VoiceDropCount = voiceCount; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs new file mode 100644 index 00000000..a1822968 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs @@ -0,0 +1,117 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// Implementation of for performance metrics version 2. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x30)] + public struct PerformanceFrameHeaderVersion2 : IPerformanceHeader + { + /// + /// The magic of the performance header. + /// + public uint Magic; + + /// + /// The total count of entries in this frame. + /// + public int EntryCount; + + /// + /// The total count of detailed entries in this frame. + /// + public int EntryDetailCount; + + /// + /// The offset of the next performance header. + /// + public int NextOffset; + + /// + /// The total time taken by all the commands profiled. + /// + public int TotalProcessingTime; + + /// + /// The count of voices that were dropped. + /// + public uint VoiceDropCount; + + /// + /// The start ticks of the . (before sending commands) + /// + public ulong StartRenderingTicks; + + /// + /// The index of this performance frame. + /// + public uint Index; + + /// + /// If set to true, the DSP is running behind. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsDspRunningBehind; + + public int GetEntryCount() + { + return EntryCount; + } + + public int GetEntryCountOffset() + { + return 4; + } + + public int GetEntryDetailCount() + { + return EntryDetailCount; + } + + public void SetDspRunningBehind(bool isRunningBehind) + { + IsDspRunningBehind = isRunningBehind; + } + + public void SetEntryCount(int entryCount) + { + EntryCount = entryCount; + } + + public void SetEntryDetailCount(int entryDetailCount) + { + EntryDetailCount = entryDetailCount; + } + + public void SetIndex(uint index) + { + Index = index; + } + + public void SetMagic(uint magic) + { + Magic = magic; + } + + public void SetNextOffset(int nextOffset) + { + NextOffset = nextOffset; + } + + public void SetStartRenderingTicks(ulong startTicks) + { + StartRenderingTicks = startTicks; + } + + public void SetTotalProcessingTime(int totalProcessingTime) + { + TotalProcessingTime = totalProcessingTime; + } + + public void SetVoiceDropCount(uint voiceCount) + { + VoiceDropCount = voiceCount; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs new file mode 100644 index 00000000..f996441c --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs @@ -0,0 +1,106 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using System; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + public abstract class PerformanceManager + { + /// + /// Get the required size for a single performance frame. + /// + /// The audio renderer configuration. + /// The behaviour context. + /// The required size for a single performance frame. + public static ulong GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref AudioRendererConfiguration parameter, ref BehaviourContext behaviourContext) + { + uint version = behaviourContext.GetPerformanceMetricsDataFormat(); + + if (version == 2) + { + return (ulong)PerformanceManagerGeneric.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter); + } + else if (version == 1) + { + return (ulong)PerformanceManagerGeneric.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter); + } + + throw new NotImplementedException($"Unknown Performance metrics data format version {version}"); + } + + /// + /// Copy the performance frame history to the supplied user buffer and returns the size copied. + /// + /// The supplied user buffer to store the performance frame into. + /// The size copied to the supplied buffer. + public abstract uint CopyHistories(Span performanceOutput); + + /// + /// Set the target node id to profile. + /// + /// The target node id to profile. + public abstract void SetTargetNodeId(int target); + + /// + /// Check if the given target node id is profiled. + /// + /// The target node id to check. + /// Return true, if the given target node id is profiled. + public abstract bool IsTargetNodeId(int target); + + /// + /// Get the next buffer to store a performance entry. + /// + /// The output . + /// The info. + /// The node id of the entry. + /// Return true, if a valid was returned. + public abstract bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceEntryType entryType, int nodeId); + + /// + /// Get the next buffer to store a performance detailed entry. + /// + /// The output . + /// The info. + /// The info. + /// The node id of the entry. + /// Return true, if a valid was returned. + public abstract bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceDetailType detailType, PerformanceEntryType entryType, int nodeId); + + /// + /// Finalize the current performance frame. + /// + /// Indicate if the DSP is running behind. + /// The count of voices that were dropped. + /// The start ticks of the audio rendering. + public abstract void TapFrame(bool dspRunningBehind, uint voiceDropCount, ulong startRenderingTicks); + + /// + /// Create a new . + /// + /// The backing memory available for use by the manager. + /// The audio renderer configuration. + /// The behaviour context; + /// A new . + public static PerformanceManager Create(Memory performanceBuffer, ref AudioRendererConfiguration parameter, BehaviourContext behaviourContext) + { + uint version = behaviourContext.GetPerformanceMetricsDataFormat(); + + switch (version) + { + case 1: + return new PerformanceManagerGeneric(performanceBuffer, + ref parameter); + case 2: + return new PerformanceManagerGeneric(performanceBuffer, + ref parameter); + default: + throw new NotImplementedException($"Unknown Performance metrics data format version {version}"); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs new file mode 100644 index 00000000..18e77391 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs @@ -0,0 +1,304 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Performance +{ + /// + /// A Generic implementation of . + /// + /// The header implementation of the performance frame. + /// The entry implementation of the performance frame. + /// A detailed implementation of the performance frame. + public class PerformanceManagerGeneric : PerformanceManager where THeader : unmanaged, IPerformanceHeader where TEntry : unmanaged, IPerformanceEntry where TEntryDetail : unmanaged, IPerformanceDetailEntry + { + /// + /// The magic used for the . + /// + private const uint MagicPerformanceBuffer = 0x46524550; + + /// + /// The fixed amount of that can be stored in a frame. + /// + private const int MaxFrameDetailCount = 100; + + private Memory _buffer; + private Memory _historyBuffer; + + private Memory CurrentBuffer => _buffer.Slice(0, _frameSize); + private Memory CurrentBufferData => CurrentBuffer.Slice(Unsafe.SizeOf()); + + private ref THeader CurrentHeader => ref MemoryMarshal.Cast(CurrentBuffer.Span)[0]; + + private Span Entries => MemoryMarshal.Cast(CurrentBufferData.Span.Slice(0, GetEntriesSize())); + private Span EntriesDetail => MemoryMarshal.Cast(CurrentBufferData.Span.Slice(GetEntriesSize(), GetEntriesDetailSize())); + + private int _frameSize; + private int _availableFrameCount; + private int _entryCountPerFrame; + private int _detailTarget; + private int _entryIndex; + private int _entryDetailIndex; + private int _indexHistoryWrite; + private int _indexHistoryRead; + private uint _historyFrameIndex; + + public PerformanceManagerGeneric(Memory buffer, ref AudioRendererConfiguration parameter) + { + _buffer = buffer; + _frameSize = GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter); + + _entryCountPerFrame = (int)GetEntryCount(ref parameter); + _availableFrameCount = buffer.Length / _frameSize - 1; + + _historyFrameIndex = 0; + + _historyBuffer = _buffer.Slice(_frameSize); + + SetupNewHeader(); + } + + private Span GetBufferFromIndex(Span data, int index) + { + return data.Slice(index * _frameSize, _frameSize); + } + + private ref THeader GetHeaderFromBuffer(Span data, int index) + { + return ref MemoryMarshal.Cast(GetBufferFromIndex(data, index))[0]; + } + + private Span GetEntriesFromBuffer(Span data, int index) + { + return MemoryMarshal.Cast(GetBufferFromIndex(data, index).Slice(Unsafe.SizeOf(), GetEntriesSize())); + } + + private Span GetEntriesDetailFromBuffer(Span data, int index) + { + return MemoryMarshal.Cast(GetBufferFromIndex(data, index).Slice(Unsafe.SizeOf() + GetEntriesSize(), GetEntriesDetailSize())); + } + + private void SetupNewHeader() + { + _entryIndex = 0; + _entryDetailIndex = 0; + + CurrentHeader.SetEntryCount(0); + CurrentHeader.SetEntryDetailCount(0); + } + + public static uint GetEntryCount(ref AudioRendererConfiguration parameter) + { + return parameter.VoiceCount + parameter.EffectCount + parameter.SubMixBufferCount + parameter.SinkCount + 1; + } + + public int GetEntriesSize() + { + return Unsafe.SizeOf() * _entryCountPerFrame; + } + + public static int GetEntriesDetailSize() + { + return Unsafe.SizeOf() * MaxFrameDetailCount; + } + + public static int GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref AudioRendererConfiguration parameter) + { + return Unsafe.SizeOf() * (int)GetEntryCount(ref parameter) + GetEntriesDetailSize() + Unsafe.SizeOf(); + } + + public override uint CopyHistories(Span performanceOutput) + { + if (performanceOutput.IsEmpty) + { + return 0; + } + + int nextOffset = 0; + + while (_indexHistoryRead != _indexHistoryWrite) + { + if (nextOffset >= performanceOutput.Length) + { + break; + } + + ref THeader inputHeader = ref GetHeaderFromBuffer(_historyBuffer.Span, _indexHistoryRead); + Span inputEntries = GetEntriesFromBuffer(_historyBuffer.Span, _indexHistoryRead); + Span inputEntriesDetail = GetEntriesDetailFromBuffer(_historyBuffer.Span, _indexHistoryRead); + + Span targetSpan = performanceOutput.Slice(nextOffset); + + // NOTE: We check for the space for two headers for the final blank header. + int requiredSpace = Unsafe.SizeOf() + Unsafe.SizeOf() * inputHeader.GetEntryCount() + + Unsafe.SizeOf() * inputHeader.GetEntryDetailCount() + + Unsafe.SizeOf(); + + if (targetSpan.Length < requiredSpace) + { + break; + } + + ref THeader outputHeader = ref MemoryMarshal.Cast(targetSpan)[0]; + + nextOffset += Unsafe.SizeOf(); + + Span outputEntries = MemoryMarshal.Cast(performanceOutput.Slice(nextOffset)); + + int totalProcessingTime = 0; + + int effectiveEntryCount = 0; + + for (int entryIndex = 0; entryIndex < inputHeader.GetEntryCount(); entryIndex++) + { + ref TEntry input = ref inputEntries[entryIndex]; + + if (input.GetProcessingTime() != 0 || input.GetStartTime() != 0) + { + ref TEntry output = ref outputEntries[effectiveEntryCount++]; + + output = input; + + nextOffset += Unsafe.SizeOf(); + + totalProcessingTime += input.GetProcessingTime(); + } + } + + Span outputEntriesDetail = MemoryMarshal.Cast(performanceOutput.Slice(nextOffset)); + + int effectiveEntryDetailCount = 0; + + for (int entryDetailIndex = 0; entryDetailIndex < inputHeader.GetEntryDetailCount(); entryDetailIndex++) + { + ref TEntryDetail input = ref inputEntriesDetail[entryDetailIndex]; + + if (input.GetProcessingTime() != 0 || input.GetStartTime() != 0) + { + ref TEntryDetail output = ref outputEntriesDetail[effectiveEntryDetailCount++]; + + output = input; + + nextOffset += Unsafe.SizeOf(); + } + } + + outputHeader = inputHeader; + outputHeader.SetMagic(MagicPerformanceBuffer); + outputHeader.SetTotalProcessingTime(totalProcessingTime); + outputHeader.SetNextOffset(nextOffset); + outputHeader.SetEntryCount(effectiveEntryCount); + outputHeader.SetEntryDetailCount(effectiveEntryDetailCount); + + _indexHistoryRead = (_indexHistoryRead + 1) % _availableFrameCount; + } + + if (nextOffset < performanceOutput.Length && (performanceOutput.Length - nextOffset) >= Unsafe.SizeOf()) + { + ref THeader outputHeader = ref MemoryMarshal.Cast(performanceOutput.Slice(nextOffset))[0]; + + outputHeader = default; + } + + return (uint)nextOffset; + } + + public override bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceEntryType entryType, int nodeId) + { + performanceEntry = new PerformanceEntryAddresses(); + performanceEntry.BaseMemory = SpanMemoryManager.Cast(CurrentBuffer); + performanceEntry.EntryCountOffset = (uint)CurrentHeader.GetEntryCountOffset(); + + uint baseEntryOffset = (uint)(Unsafe.SizeOf() + Unsafe.SizeOf() * _entryIndex); + + ref TEntry entry = ref Entries[_entryIndex]; + + performanceEntry.StartTimeOffset = baseEntryOffset + (uint)entry.GetStartTimeOffset(); + performanceEntry.ProcessingTimeOffset = baseEntryOffset + (uint)entry.GetProcessingTimeOffset(); + + entry = default; + entry.SetEntryType(entryType); + entry.SetNodeId(nodeId); + + _entryIndex++; + + return true; + } + + public override bool GetNextEntry(out PerformanceEntryAddresses performanceEntry, PerformanceDetailType detailType, PerformanceEntryType entryType, int nodeId) + { + performanceEntry = null; + + if (_entryDetailIndex > MaxFrameDetailCount) + { + return false; + } + + performanceEntry = new PerformanceEntryAddresses(); + performanceEntry.BaseMemory = SpanMemoryManager.Cast(CurrentBuffer); + performanceEntry.EntryCountOffset = (uint)CurrentHeader.GetEntryCountOffset(); + + uint baseEntryOffset = (uint)(Unsafe.SizeOf() + GetEntriesSize() + Unsafe.SizeOf() * _entryDetailIndex); + + ref TEntryDetail entryDetail = ref EntriesDetail[_entryDetailIndex]; + + performanceEntry.StartTimeOffset = baseEntryOffset + (uint)entryDetail.GetStartTimeOffset(); + performanceEntry.ProcessingTimeOffset = baseEntryOffset + (uint)entryDetail.GetProcessingTimeOffset(); + + entryDetail = default; + entryDetail.SetDetailType(detailType); + entryDetail.SetEntryType(entryType); + entryDetail.SetNodeId(nodeId); + + _entryDetailIndex++; + + return true; + } + + public override bool IsTargetNodeId(int target) + { + return _detailTarget == target; + } + + public override void SetTargetNodeId(int target) + { + _detailTarget = target; + } + + public override void TapFrame(bool dspRunningBehind, uint voiceDropCount, ulong startRenderingTicks) + { + if (_availableFrameCount > 0) + { + int targetIndexForHistory = _indexHistoryWrite; + + _indexHistoryWrite = (_indexHistoryWrite + 1) % _availableFrameCount; + + ref THeader targetHeader = ref GetHeaderFromBuffer(_historyBuffer.Span, targetIndexForHistory); + + CurrentBuffer.Span.CopyTo(GetBufferFromIndex(_historyBuffer.Span, targetIndexForHistory)); + + uint targetHistoryFrameIndex = _historyFrameIndex; + + if (_historyFrameIndex == uint.MaxValue) + { + _historyFrameIndex = 0; + } + else + { + _historyFrameIndex++; + } + + targetHeader.SetDspRunningBehind(dspRunningBehind); + targetHeader.SetVoiceDropCount(voiceDropCount); + targetHeader.SetStartRenderingTicks(startRenderingTicks); + targetHeader.SetIndex(targetHistoryFrameIndex); + + // Finally setup the new header + SetupNewHeader(); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/RendererSystemContext.cs b/src/Ryujinx.Audio/Renderer/Server/RendererSystemContext.cs new file mode 100644 index 00000000..16456780 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/RendererSystemContext.cs @@ -0,0 +1,48 @@ +using Ryujinx.Audio.Renderer.Server.Upsampler; +using System; + +namespace Ryujinx.Audio.Renderer.Server +{ + /// + /// Represents a lite version of used by the + /// + /// + /// This also allows to reduce dependencies on the for unit testing. + /// + public sealed class RendererSystemContext + { + /// + /// The session id of the current renderer. + /// + public int SessionId; + + /// + /// The target channel count for sink. + /// + /// See for usage. + public uint ChannelCount; + + /// + /// The total count of mix buffer. + /// + public uint MixBufferCount; + + /// + /// Instance of the used to derive bug fixes and features of the current audio renderer revision. + /// + public BehaviourContext BehaviourContext; + + /// + /// Instance of the used for upsampling (see ) + /// + public UpsamplerManager UpsamplerManager; + + /// + /// The memory to use for depop processing. + /// + /// + /// See and + /// + public Memory DepopBuffer; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs b/src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs new file mode 100644 index 00000000..f7b63997 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs @@ -0,0 +1,102 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Diagnostics; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +namespace Ryujinx.Audio.Renderer.Server.Sink +{ + /// + /// Base class used for server information of a sink. + /// + public class BaseSink + { + /// + /// The type of this . + /// + public SinkType Type; + + /// + /// Set to true if the sink is used. + /// + public bool IsUsed; + + /// + /// Set to true if the sink need to be skipped because of invalid state. + /// + public bool ShouldSkip; + + /// + /// The node id of the sink. + /// + public int NodeId; + + /// + /// Create a new . + /// + public BaseSink() + { + CleanUp(); + } + + /// + /// Clean up the internal state of the . + /// + public virtual void CleanUp() + { + Type = TargetSinkType; + IsUsed = false; + ShouldSkip = false; + } + + /// + /// The target handled by this . + /// + public virtual SinkType TargetSinkType => SinkType.Invalid; + + /// + /// Check if the sent by the user match the internal . + /// + /// The user parameter. + /// Return true, if the sent by the user match the internal . + public bool IsTypeValid(ref SinkInParameter parameter) + { + return parameter.Type == TargetSinkType; + } + + /// + /// Update the state during command generation. + /// + public virtual void UpdateForCommandGeneration() + { + Debug.Assert(Type == TargetSinkType); + } + + /// + /// Update the internal common parameters from user parameter. + /// + /// The user parameter. + protected void UpdateStandardParameter(ref SinkInParameter parameter) + { + if (IsUsed != parameter.IsUsed) + { + IsUsed = parameter.IsUsed; + NodeId = parameter.NodeId; + } + } + + /// + /// Update the internal state from user parameter. + /// + /// The possible that was generated. + /// The user parameter. + /// The user output status. + /// The mapper to use. + public virtual void Update(out ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(ref parameter)); + + errorInfo = new ErrorInfo(); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs b/src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs new file mode 100644 index 00000000..722d8c4b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs @@ -0,0 +1,109 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Sink; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Sink +{ + /// + /// Server information for a circular buffer sink. + /// + public class CircularBufferSink : BaseSink + { + /// + /// The circular buffer parameter. + /// + public CircularBufferParameter Parameter; + + /// + /// The last written data offset on the circular buffer. + /// + private uint _lastWrittenOffset; + + /// + /// THe previous written offset of the circular buffer. + /// + private uint _oldWrittenOffset; + + /// + /// The current offset to write data on the circular buffer. + /// + public uint CurrentWriteOffset { get; private set; } + + /// + /// The of the circular buffer. + /// + public AddressInfo CircularBufferAddressInfo; + + public CircularBufferSink() + { + CircularBufferAddressInfo = AddressInfo.Create(); + } + + public override SinkType TargetSinkType => SinkType.CircularBuffer; + + public override void Update(out BehaviourParameter.ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) + { + errorInfo = new BehaviourParameter.ErrorInfo(); + outStatus = new SinkOutStatus(); + + Debug.Assert(IsTypeValid(ref parameter)); + + ref CircularBufferParameter inputDeviceParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + if (parameter.IsUsed != IsUsed || ShouldSkip) + { + UpdateStandardParameter(ref parameter); + + if (parameter.IsUsed) + { + Debug.Assert(CircularBufferAddressInfo.CpuAddress == 0); + Debug.Assert(CircularBufferAddressInfo.GetReference(false) == 0); + + ShouldSkip = !mapper.TryAttachBuffer(out errorInfo, ref CircularBufferAddressInfo, inputDeviceParameter.BufferAddress, inputDeviceParameter.BufferSize); + } + else + { + Debug.Assert(CircularBufferAddressInfo.CpuAddress != 0); + Debug.Assert(CircularBufferAddressInfo.GetReference(false) != 0); + } + + Parameter = inputDeviceParameter; + } + + outStatus.LastWrittenOffset = _lastWrittenOffset; + } + + public override void UpdateForCommandGeneration() + { + Debug.Assert(Type == TargetSinkType); + + if (IsUsed) + { + uint frameSize = Constants.TargetSampleSize * Parameter.SampleCount * Parameter.InputCount; + + _lastWrittenOffset = _oldWrittenOffset; + + _oldWrittenOffset = CurrentWriteOffset; + + CurrentWriteOffset += frameSize; + + if (Parameter.BufferSize > 0) + { + CurrentWriteOffset %= Parameter.BufferSize; + } + } + } + + public override void CleanUp() + { + CircularBufferAddressInfo = AddressInfo.Create(); + _lastWrittenOffset = 0; + _oldWrittenOffset = 0; + CurrentWriteOffset = 0; + base.CleanUp(); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs b/src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs new file mode 100644 index 00000000..de345d3a --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs @@ -0,0 +1,75 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Sink; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using Ryujinx.Audio.Renderer.Server.Upsampler; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Sink +{ + /// + /// Server information for a device sink. + /// + public class DeviceSink : BaseSink + { + /// + /// The downmix coefficients. + /// + public float[] DownMixCoefficients; + + /// + /// The device parameters. + /// + public DeviceParameter Parameter; + + /// + /// The upsampler instance used by this sink. + /// + /// Null if no upsampling is needed. + public UpsamplerState UpsamplerState; + + /// + /// Create a new . + /// + public DeviceSink() + { + DownMixCoefficients = new float[4]; + } + + public override void CleanUp() + { + UpsamplerState?.Release(); + + UpsamplerState = null; + + base.CleanUp(); + } + + public override SinkType TargetSinkType => SinkType.Device; + + public override void Update(out BehaviourParameter.ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) + { + Debug.Assert(IsTypeValid(ref parameter)); + + ref DeviceParameter inputDeviceParameter = ref MemoryMarshal.Cast(parameter.SpecificData)[0]; + + if (parameter.IsUsed != IsUsed) + { + UpdateStandardParameter(ref parameter); + Parameter = inputDeviceParameter; + } + else + { + Parameter.DownMixParameterEnabled = inputDeviceParameter.DownMixParameterEnabled; + inputDeviceParameter.DownMixParameter.AsSpan().CopyTo(Parameter.DownMixParameter.AsSpan()); + } + + Parameter.DownMixParameter.AsSpan().CopyTo(DownMixCoefficients.AsSpan()); + + errorInfo = new BehaviourParameter.ErrorInfo(); + outStatus = new SinkOutStatus(); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Sink/SinkContext.cs b/src/Ryujinx.Audio/Renderer/Server/Sink/SinkContext.cs new file mode 100644 index 00000000..b57d3990 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Sink/SinkContext.cs @@ -0,0 +1,56 @@ +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server.Sink +{ + /// + /// Sink context. + /// + public class SinkContext + { + /// + /// Storage for . + /// + private BaseSink[] _sinks; + + /// + /// The total sink count. + /// + private uint _sinkCount; + + /// + /// Initialize the . + /// + /// The total sink count. + public void Initialize(uint sinksCount) + { + _sinkCount = sinksCount; + _sinks = new BaseSink[_sinkCount]; + + for (int i = 0; i < _sinkCount; i++) + { + _sinks[i] = new BaseSink(); + } + } + + /// + /// Get the total sink count. + /// + /// The total sink count. + public uint GetCount() + { + return _sinkCount; + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref BaseSink GetSink(int id) + { + Debug.Assert(id >= 0 && id < _sinkCount); + + return ref _sinks[id]; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs new file mode 100644 index 00000000..91877cdd --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs @@ -0,0 +1,303 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common; +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Splitter +{ + /// + /// Splitter context. + /// + public class SplitterContext + { + /// + /// Storage for . + /// + private Memory _splitters; + + /// + /// Storage for . + /// + private Memory _splitterDestinations; + + /// + /// If set to true, trust the user destination count in . + /// + public bool IsBugFixed { get; private set; } + + /// + /// Initialize . + /// + /// The behaviour context. + /// The audio renderer configuration. + /// The . + /// Return true if the initialization was successful. + public bool Initialize(ref BehaviourContext behaviourContext, ref AudioRendererConfiguration parameter, WorkBufferAllocator workBufferAllocator) + { + if (!behaviourContext.IsSplitterSupported() || parameter.SplitterCount <= 0 || parameter.SplitterDestinationCount <= 0) + { + Setup(Memory.Empty, Memory.Empty, false); + + return true; + } + + Memory splitters = workBufferAllocator.Allocate(parameter.SplitterCount, SplitterState.Alignment); + + if (splitters.IsEmpty) + { + return false; + } + + int splitterId = 0; + + foreach (ref SplitterState splitter in splitters.Span) + { + splitter = new SplitterState(splitterId++); + } + + Memory splitterDestinations = workBufferAllocator.Allocate(parameter.SplitterDestinationCount, + SplitterDestination.Alignment); + + if (splitterDestinations.IsEmpty) + { + return false; + } + + int splitterDestinationId = 0; + foreach (ref SplitterDestination data in splitterDestinations.Span) + { + data = new SplitterDestination(splitterDestinationId++); + } + + SplitterState.InitializeSplitters(splitters.Span); + + Setup(splitters, splitterDestinations, behaviourContext.IsSplitterBugFixed()); + + return true; + } + + /// + /// Get the work buffer size while adding the size needed for splitter to operate. + /// + /// The current size. + /// The behaviour context. + /// The renderer configuration. + /// Return the new size taking splitter into account. + public static ulong GetWorkBufferSize(ulong size, ref BehaviourContext behaviourContext, ref AudioRendererConfiguration parameter) + { + if (behaviourContext.IsSplitterSupported()) + { + size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterCount, SplitterState.Alignment); + size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, SplitterDestination.Alignment); + + if (behaviourContext.IsSplitterBugFixed()) + { + size = WorkBufferAllocator.GetTargetSize(size, parameter.SplitterDestinationCount, 0x10); + } + + return size; + } + else + { + return size; + } + } + + /// + /// Setup the instance. + /// + /// The storage. + /// The storage. + /// If set to true, trust the user destination count in . + private void Setup(Memory splitters, Memory splitterDestinations, bool isBugFixed) + { + _splitters = splitters; + _splitterDestinations = splitterDestinations; + IsBugFixed = isBugFixed; + } + + /// + /// Clear the new connection flag. + /// + private void ClearAllNewConnectionFlag() + { + foreach (ref SplitterState splitter in _splitters.Span) + { + splitter.ClearNewConnectionFlag(); + } + } + + /// + /// Get the destination count using the count of splitter. + /// + /// The destination count using the count of splitter. + public int GetDestinationCountPerStateForCompatibility() + { + if (_splitters.IsEmpty) + { + return 0; + } + + return _splitterDestinations.Length / _splitters.Length; + } + + /// + /// Update one or multiple from user parameters. + /// + /// The splitter header. + /// The raw data after the splitter header. + private void UpdateState(scoped ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan input) + { + for (int i = 0; i < inputHeader.SplitterCount; i++) + { + SplitterInParameter parameter = MemoryMarshal.Read(input); + + Debug.Assert(parameter.IsMagicValid()); + + if (parameter.IsMagicValid()) + { + if (parameter.Id >= 0 && parameter.Id < _splitters.Length) + { + ref SplitterState splitter = ref GetState(parameter.Id); + + splitter.Update(this, ref parameter, input.Slice(Unsafe.SizeOf())); + } + + input = input.Slice(0x1C + (int)parameter.DestinationCount * 4); + } + } + } + + /// + /// Update one or multiple from user parameters. + /// + /// The splitter header. + /// The raw data after the splitter header. + private void UpdateData(scoped ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan input) + { + for (int i = 0; i < inputHeader.SplitterDestinationCount; i++) + { + SplitterDestinationInParameter parameter = MemoryMarshal.Read(input); + + Debug.Assert(parameter.IsMagicValid()); + + if (parameter.IsMagicValid()) + { + if (parameter.Id >= 0 && parameter.Id < _splitterDestinations.Length) + { + ref SplitterDestination destination = ref GetDestination(parameter.Id); + + destination.Update(parameter); + } + + input = input.Slice(Unsafe.SizeOf()); + } + } + } + + /// + /// Update splitter from user parameters. + /// + /// The input raw user data. + /// The total consumed size. + /// Return true if the update was successful. + public bool Update(ReadOnlySpan input, out int consumedSize) + { + if (_splitterDestinations.IsEmpty || _splitters.IsEmpty) + { + consumedSize = 0; + + return true; + } + + int originalSize = input.Length; + + SplitterInParameterHeader header = SpanIOHelper.Read(ref input); + + if (header.IsMagicValid()) + { + ClearAllNewConnectionFlag(); + + UpdateState(ref header, ref input); + UpdateData(ref header, ref input); + + consumedSize = BitUtils.AlignUp(originalSize - input.Length, 0x10); + + return true; + } + else + { + consumedSize = 0; + + return false; + } + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref SplitterState GetState(int id) + { + return ref SpanIOHelper.GetFromMemory(_splitters, id, (uint)_splitters.Length); + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref SplitterDestination GetDestination(int id) + { + return ref SpanIOHelper.GetFromMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length); + } + + /// + /// Get a at the given . + /// + /// The index to use. + /// A at the given . + public Memory GetDestinationMemory(int id) + { + return SpanIOHelper.GetMemory(_splitterDestinations, id, (uint)_splitterDestinations.Length); + } + + /// + /// Get a in the at and pass to . + /// + /// The index to use to get the . + /// The index of the . + /// A . + public Span GetDestination(int id, int destinationId) + { + ref SplitterState splitter = ref GetState(id); + + return splitter.GetData(destinationId); + } + + /// + /// Return true if the audio renderer has any splitters. + /// + /// True if the audio renderer has any splitters. + public bool UsingSplitter() + { + return !_splitters.IsEmpty && !_splitterDestinations.IsEmpty; + } + + /// + /// Update the internal state of all splitters. + /// + public void UpdateInternalState() + { + foreach (ref SplitterState splitter in _splitters.Span) + { + splitter.UpdateInternalState(); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs new file mode 100644 index 00000000..c074e4a7 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs @@ -0,0 +1,193 @@ +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Common.Utilities; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Splitter +{ + /// + /// Server state for a splitter destination. + /// + [StructLayout(LayoutKind.Sequential, Size = 0xE0, Pack = Alignment)] + public struct SplitterDestination + { + public const int Alignment = 0x10; + + /// + /// The unique id of this . + /// + public int Id; + + /// + /// The mix to output the result of the splitter. + /// + public int DestinationId; + + /// + /// Mix buffer volumes storage. + /// + private MixArray _mix; + private MixArray _previousMix; + + /// + /// Pointer to the next linked element. + /// + private unsafe SplitterDestination* _next; + + /// + /// Set to true if in use. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + /// + /// Set to true if the internal state need to be updated. + /// + [MarshalAs(UnmanagedType.I1)] + public bool NeedToUpdateInternalState; + + [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)] + private struct MixArray { } + + /// + /// Mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span MixBufferVolume => SpanHelpers.AsSpan(ref _mix); + + /// + /// Previous mix buffer volumes. + /// + /// Used when a splitter id is specified in the mix. + public Span PreviousMixBufferVolume => SpanHelpers.AsSpan(ref _previousMix); + + /// + /// Get the of the next element or if not present. + /// + public Span Next + { + get + { + unsafe + { + return _next != null ? new Span(_next, 1) : Span.Empty; + } + } + } + + /// + /// Create a new . + /// + /// The unique id of this . + public SplitterDestination(int id) : this() + { + Id = id; + DestinationId = Constants.UnusedMixId; + + ClearVolumes(); + } + + /// + /// Update the from user parameter. + /// + /// The user parameter. + public void Update(SplitterDestinationInParameter parameter) + { + Debug.Assert(Id == parameter.Id); + + if (parameter.IsMagicValid() && Id == parameter.Id) + { + DestinationId = parameter.DestinationId; + + parameter.MixBufferVolume.CopyTo(MixBufferVolume); + + if (!IsUsed && parameter.IsUsed) + { + MixBufferVolume.CopyTo(PreviousMixBufferVolume); + + NeedToUpdateInternalState = false; + } + + IsUsed = parameter.IsUsed; + } + } + + /// + /// Update the internal state of the instance. + /// + public void UpdateInternalState() + { + if (IsUsed && NeedToUpdateInternalState) + { + MixBufferVolume.CopyTo(PreviousMixBufferVolume); + } + + NeedToUpdateInternalState = false; + } + + /// + /// Set the update internal state marker. + /// + public void MarkAsNeedToUpdateInternalState() + { + NeedToUpdateInternalState = true; + } + + /// + /// Return true if the is used and has a destination. + /// + /// True if the is used and has a destination. + public bool IsConfigured() + { + return IsUsed && DestinationId != Constants.UnusedMixId; + } + + /// + /// Get the volume for a given destination. + /// + /// The destination index to use. + /// The volume for the given destination. + public float GetMixVolume(int destinationIndex) + { + Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax); + + return MixBufferVolume[destinationIndex]; + } + + /// + /// Clear the volumes. + /// + public void ClearVolumes() + { + MixBufferVolume.Fill(0); + PreviousMixBufferVolume.Fill(0); + } + + /// + /// Link the next element to the given . + /// + /// The given to link. + public void Link(ref SplitterDestination next) + { + unsafe + { + fixed (SplitterDestination* nextPtr = &next) + { + _next = nextPtr; + } + } + } + + /// + /// Remove the link to the next element. + /// + public void Unlink() + { + unsafe + { + _next = null; + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs new file mode 100644 index 00000000..15a0c6ba --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs @@ -0,0 +1,221 @@ +using Ryujinx.Audio.Renderer.Parameter; +using System; +using System.Buffers; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Splitter +{ + /// + /// Server state for a splitter. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = Alignment)] + public struct SplitterState + { + public const int Alignment = 0x10; + + /// + /// The unique id of this . + /// + public int Id; + + /// + /// Target sample rate to use on the splitter. + /// + public uint SampleRate; + + /// + /// Count of splitter destinations (). + /// + public int DestinationCount; + + /// + /// Set to true if the splitter has a new connection. + /// + [MarshalAs(UnmanagedType.I1)] + public bool HasNewConnection; + + /// + /// Linked list of . + /// + private unsafe SplitterDestination* _destinationsData; + + /// + /// Span to the first element of the linked list of . + /// + public Span Destinations + { + get + { + unsafe + { + return (IntPtr)_destinationsData != IntPtr.Zero ? new Span(_destinationsData, 1) : Span.Empty; + } + } + } + + /// + /// Create a new . + /// + /// The unique id of this . + public SplitterState(int id) : this() + { + Id = id; + } + + public Span GetData(int index) + { + int i = 0; + + Span result = Destinations; + + while (i < index) + { + if (result.IsEmpty) + { + break; + } + + result = result[0].Next; + i++; + } + + return result; + } + + /// + /// Clear the new connection flag. + /// + public void ClearNewConnectionFlag() + { + HasNewConnection = false; + } + + /// + /// Utility function to apply a given to all . + /// + /// The action to execute on each elements. + private void ForEachDestination(SpanAction action) + { + Span temp = Destinations; + + int i = 0; + + while (true) + { + if (temp.IsEmpty) + { + break; + } + + Span next = temp[0].Next; + + action.Invoke(temp, i++); + + temp = next; + } + } + + /// + /// Update the from user parameter. + /// + /// The splitter context. + /// The user parameter. + /// The raw input data after the . + public void Update(SplitterContext context, ref SplitterInParameter parameter, ReadOnlySpan input) + { + ClearLinks(); + + int destinationCount; + + if (context.IsBugFixed) + { + destinationCount = parameter.DestinationCount; + } + else + { + destinationCount = Math.Min(context.GetDestinationCountPerStateForCompatibility(), parameter.DestinationCount); + } + + if (destinationCount > 0) + { + ReadOnlySpan destinationIds = MemoryMarshal.Cast(input); + + Memory destination = context.GetDestinationMemory(destinationIds[0]); + + SetDestination(ref destination.Span[0]); + + DestinationCount = destinationCount; + + for (int i = 1; i < destinationCount; i++) + { + Memory nextDestination = context.GetDestinationMemory(destinationIds[i]); + + destination.Span[0].Link(ref nextDestination.Span[0]); + destination = nextDestination; + } + } + + Debug.Assert(parameter.Id == Id); + + if (parameter.Id == Id) + { + SampleRate = parameter.SampleRate; + HasNewConnection = true; + } + } + + /// + /// Set the head of the linked list of . + /// + /// A reference to a . + public void SetDestination(ref SplitterDestination newValue) + { + unsafe + { + fixed (SplitterDestination* newValuePtr = &newValue) + { + _destinationsData = newValuePtr; + } + } + } + + /// + /// Update the internal state of this instance. + /// + public void UpdateInternalState() + { + ForEachDestination((destination, _) => destination[0].UpdateInternalState()); + } + + /// + /// Clear all links from the . + /// + public void ClearLinks() + { + ForEachDestination((destination, _) => destination[0].Unlink()); + + unsafe + { + _destinationsData = (SplitterDestination*)IntPtr.Zero; + } + } + + /// + /// Initialize a given . + /// + /// All the to initialize. + public static void InitializeSplitters(Span splitters) + { + foreach (ref SplitterState splitter in splitters) + { + unsafe + { + splitter._destinationsData = (SplitterDestination*)IntPtr.Zero; + } + + splitter.DestinationCount = 0; + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs b/src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs new file mode 100644 index 00000000..5cf539c6 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs @@ -0,0 +1,643 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Parameter.Performance; +using Ryujinx.Audio.Renderer.Server.Effect; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using Ryujinx.Audio.Renderer.Server.Mix; +using Ryujinx.Audio.Renderer.Server.Performance; +using Ryujinx.Audio.Renderer.Server.Sink; +using Ryujinx.Audio.Renderer.Server.Splitter; +using Ryujinx.Audio.Renderer.Server.Voice; +using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common.Logging; +using System; +using System.Buffers; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; + +namespace Ryujinx.Audio.Renderer.Server +{ + public class StateUpdater + { + private readonly ReadOnlyMemory _inputOrigin; + private ReadOnlyMemory _outputOrigin; + private ReadOnlyMemory _input; + + private Memory _output; + private uint _processHandle; + private BehaviourContext _behaviourContext; + + private UpdateDataHeader _inputHeader; + private Memory _outputHeader; + + private ref UpdateDataHeader OutputHeader => ref _outputHeader.Span[0]; + + public StateUpdater(ReadOnlyMemory input, Memory output, uint processHandle, BehaviourContext behaviourContext) + { + _input = input; + _inputOrigin = _input; + _output = output; + _outputOrigin = _output; + _processHandle = processHandle; + _behaviourContext = behaviourContext; + + _inputHeader = SpanIOHelper.Read(ref _input); + + _outputHeader = SpanMemoryManager.Cast(_output.Slice(0, Unsafe.SizeOf())); + OutputHeader.Initialize(_behaviourContext.UserRevision); + _output = _output.Slice(Unsafe.SizeOf()); + } + + public ResultCode UpdateBehaviourContext() + { + BehaviourParameter parameter = SpanIOHelper.Read(ref _input); + + if (!BehaviourContext.CheckValidRevision(parameter.UserRevision) || parameter.UserRevision != _behaviourContext.UserRevision) + { + return ResultCode.InvalidUpdateInfo; + } + + _behaviourContext.ClearError(); + _behaviourContext.UpdateFlags(parameter.Flags); + + if (_inputHeader.BehaviourSize != Unsafe.SizeOf()) + { + return ResultCode.InvalidUpdateInfo; + } + + return ResultCode.Success; + } + + public ResultCode UpdateMemoryPools(Span memoryPools) + { + PoolMapper mapper = new PoolMapper(_processHandle, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + + if (memoryPools.Length * Unsafe.SizeOf() != _inputHeader.MemoryPoolsSize) + { + return ResultCode.InvalidUpdateInfo; + } + + foreach (ref MemoryPoolState memoryPool in memoryPools) + { + MemoryPoolInParameter parameter = SpanIOHelper.Read(ref _input); + + ref MemoryPoolOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + PoolMapper.UpdateResult updateResult = mapper.Update(ref memoryPool, ref parameter, ref outStatus); + + if (updateResult != PoolMapper.UpdateResult.Success && + updateResult != PoolMapper.UpdateResult.MapError && + updateResult != PoolMapper.UpdateResult.UnmapError) + { + if (updateResult != PoolMapper.UpdateResult.InvalidParameter) + { + throw new InvalidOperationException($"{updateResult}"); + } + + return ResultCode.InvalidUpdateInfo; + } + } + + OutputHeader.MemoryPoolsSize = (uint)(Unsafe.SizeOf() * memoryPools.Length); + OutputHeader.TotalSize += OutputHeader.MemoryPoolsSize; + + return ResultCode.Success; + } + + public ResultCode UpdateVoiceChannelResources(VoiceContext context) + { + if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.VoiceResourcesSize) + { + return ResultCode.InvalidUpdateInfo; + } + + for (int i = 0; i < context.GetCount(); i++) + { + VoiceChannelResourceInParameter parameter = SpanIOHelper.Read(ref _input); + + ref VoiceChannelResource resource = ref context.GetChannelResource(i); + + resource.Id = parameter.Id; + parameter.Mix.AsSpan().CopyTo(resource.Mix.AsSpan()); + resource.IsUsed = parameter.IsUsed; + } + + return ResultCode.Success; + } + + public ResultCode UpdateVoices(VoiceContext context, Memory memoryPools) + { + if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.VoicesSize) + { + return ResultCode.InvalidUpdateInfo; + } + + int initialOutputSize = _output.Length; + + ReadOnlySpan parameters = MemoryMarshal.Cast(_input.Slice(0, (int)_inputHeader.VoicesSize).Span); + + _input = _input.Slice((int)_inputHeader.VoicesSize); + + PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + + // First make everything not in use. + for (int i = 0; i < context.GetCount(); i++) + { + ref VoiceState state = ref context.GetState(i); + + state.InUse = false; + } + + Memory[] voiceUpdateStatesArray = ArrayPool>.Shared.Rent(Constants.VoiceChannelCountMax); + + Span> voiceUpdateStates = voiceUpdateStatesArray.AsSpan(0, Constants.VoiceChannelCountMax); + + // Start processing + for (int i = 0; i < context.GetCount(); i++) + { + VoiceInParameter parameter = parameters[i]; + + voiceUpdateStates.Fill(Memory.Empty); + + ref VoiceOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + if (parameter.InUse) + { + ref VoiceState currentVoiceState = ref context.GetState(i); + + for (int channelResourceIndex = 0; channelResourceIndex < parameter.ChannelCount; channelResourceIndex++) + { + int channelId = parameter.ChannelResourceIds[channelResourceIndex]; + + Debug.Assert(channelId >= 0 && channelId < context.GetCount()); + + voiceUpdateStates[channelResourceIndex] = context.GetUpdateStateForCpu(channelId); + } + + if (parameter.IsNew) + { + currentVoiceState.Initialize(); + } + + currentVoiceState.UpdateParameters(out ErrorInfo updateParameterError, ref parameter, ref mapper, ref _behaviourContext); + + if (updateParameterError.ErrorCode != ResultCode.Success) + { + _behaviourContext.AppendError(ref updateParameterError); + } + + currentVoiceState.UpdateWaveBuffers(out ErrorInfo[] waveBufferUpdateErrorInfos, ref parameter, voiceUpdateStates, ref mapper, ref _behaviourContext); + + foreach (ref ErrorInfo errorInfo in waveBufferUpdateErrorInfos.AsSpan()) + { + if (errorInfo.ErrorCode != ResultCode.Success) + { + _behaviourContext.AppendError(ref errorInfo); + } + } + + currentVoiceState.WriteOutStatus(ref outStatus, ref parameter, voiceUpdateStates); + } + } + + ArrayPool>.Shared.Return(voiceUpdateStatesArray); + + int currentOutputSize = _output.Length; + + OutputHeader.VoicesSize = (uint)(Unsafe.SizeOf() * context.GetCount()); + OutputHeader.TotalSize += OutputHeader.VoicesSize; + + Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.VoicesSize); + + return ResultCode.Success; + } + + private static void ResetEffect(ref BaseEffect effect, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + { + effect.ForceUnmapBuffers(mapper); + + switch (parameter.Type) + { + case EffectType.Invalid: + effect = new BaseEffect(); + break; + case EffectType.BufferMix: + effect = new BufferMixEffect(); + break; + case EffectType.AuxiliaryBuffer: + effect = new AuxiliaryBufferEffect(); + break; + case EffectType.Delay: + effect = new DelayEffect(); + break; + case EffectType.Reverb: + effect = new ReverbEffect(); + break; + case EffectType.Reverb3d: + effect = new Reverb3dEffect(); + break; + case EffectType.BiquadFilter: + effect = new BiquadFilterEffect(); + break; + case EffectType.Limiter: + effect = new LimiterEffect(); + break; + case EffectType.CaptureBuffer: + effect = new CaptureBufferEffect(); + break; + case EffectType.Compressor: + effect = new CompressorEffect(); + break; + + default: + throw new NotImplementedException($"EffectType {parameter.Type} not implemented!"); + } + } + + public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, Memory memoryPools) + { + if (_behaviourContext.IsEffectInfoVersion2Supported()) + { + return UpdateEffectsVersion2(context, isAudioRendererActive, memoryPools); + } + else + { + return UpdateEffectsVersion1(context, isAudioRendererActive, memoryPools); + } + } + + public ResultCode UpdateEffectsVersion2(EffectContext context, bool isAudioRendererActive, Memory memoryPools) + { + if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.EffectsSize) + { + return ResultCode.InvalidUpdateInfo; + } + + int initialOutputSize = _output.Length; + + ReadOnlySpan parameters = MemoryMarshal.Cast(_input.Slice(0, (int)_inputHeader.EffectsSize).Span); + + _input = _input.Slice((int)_inputHeader.EffectsSize); + + PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + + for (int i = 0; i < context.GetCount(); i++) + { + EffectInParameterVersion2 parameter = parameters[i]; + + ref EffectOutStatusVersion2 outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + ref BaseEffect effect = ref context.GetEffect(i); + + if (!effect.IsTypeValid(ref parameter)) + { + ResetEffect(ref effect, ref parameter, mapper); + } + + effect.Update(out ErrorInfo updateErrorInfo, ref parameter, mapper); + + if (updateErrorInfo.ErrorCode != ResultCode.Success) + { + _behaviourContext.AppendError(ref updateErrorInfo); + } + + effect.StoreStatus(ref outStatus, isAudioRendererActive); + + if (parameter.IsNew) + { + effect.InitializeResultState(ref context.GetDspState(i)); + effect.InitializeResultState(ref context.GetState(i)); + } + + effect.UpdateResultState(ref outStatus.ResultState, ref context.GetState(i)); + } + + int currentOutputSize = _output.Length; + + OutputHeader.EffectsSize = (uint)(Unsafe.SizeOf() * context.GetCount()); + OutputHeader.TotalSize += OutputHeader.EffectsSize; + + Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize); + + return ResultCode.Success; + } + + public ResultCode UpdateEffectsVersion1(EffectContext context, bool isAudioRendererActive, Memory memoryPools) + { + if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.EffectsSize) + { + return ResultCode.InvalidUpdateInfo; + } + + int initialOutputSize = _output.Length; + + ReadOnlySpan parameters = MemoryMarshal.Cast(_input.Slice(0, (int)_inputHeader.EffectsSize).Span); + + _input = _input.Slice((int)_inputHeader.EffectsSize); + + PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + + for (int i = 0; i < context.GetCount(); i++) + { + EffectInParameterVersion1 parameter = parameters[i]; + + ref EffectOutStatusVersion1 outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + ref BaseEffect effect = ref context.GetEffect(i); + + if (!effect.IsTypeValid(ref parameter)) + { + ResetEffect(ref effect, ref parameter, mapper); + } + + effect.Update(out ErrorInfo updateErrorInfo, ref parameter, mapper); + + if (updateErrorInfo.ErrorCode != ResultCode.Success) + { + _behaviourContext.AppendError(ref updateErrorInfo); + } + + effect.StoreStatus(ref outStatus, isAudioRendererActive); + } + + int currentOutputSize = _output.Length; + + OutputHeader.EffectsSize = (uint)(Unsafe.SizeOf() * context.GetCount()); + OutputHeader.TotalSize += OutputHeader.EffectsSize; + + Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize); + + return ResultCode.Success; + } + + public ResultCode UpdateSplitter(SplitterContext context) + { + if (context.Update(_input.Span, out int consumedSize)) + { + _input = _input.Slice(consumedSize); + + return ResultCode.Success; + } + else + { + return ResultCode.InvalidUpdateInfo; + } + } + + private bool CheckMixParametersValidity(MixContext mixContext, uint mixBufferCount, uint inputMixCount, ReadOnlySpan parameters) + { + uint maxMixStateCount = mixContext.GetCount(); + uint totalRequiredMixBufferCount = 0; + + for (int i = 0; i < inputMixCount; i++) + { + if (parameters[i].IsUsed) + { + if (parameters[i].DestinationMixId != Constants.UnusedMixId && + parameters[i].DestinationMixId > maxMixStateCount && + parameters[i].MixId != Constants.FinalMixId) + { + return true; + } + + totalRequiredMixBufferCount += parameters[i].BufferCount; + } + } + + return totalRequiredMixBufferCount > mixBufferCount; + } + + public ResultCode UpdateMixes(MixContext mixContext, uint mixBufferCount, EffectContext effectContext, SplitterContext splitterContext) + { + uint mixCount; + uint inputMixSize; + uint inputSize = 0; + + if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()) + { + MixInParameterDirtyOnlyUpdate parameter = MemoryMarshal.Cast(_input.Span)[0]; + + mixCount = parameter.MixCount; + + inputSize += (uint)Unsafe.SizeOf(); + } + else + { + mixCount = mixContext.GetCount(); + } + + inputMixSize = mixCount * (uint)Unsafe.SizeOf(); + + inputSize += inputMixSize; + + if (inputSize != _inputHeader.MixesSize) + { + return ResultCode.InvalidUpdateInfo; + } + + if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()) + { + _input = _input.Slice(Unsafe.SizeOf()); + } + + ReadOnlySpan parameters = MemoryMarshal.Cast(_input.Span.Slice(0, (int)inputMixSize)); + + _input = _input.Slice((int)inputMixSize); + + if (CheckMixParametersValidity(mixContext, mixBufferCount, mixCount, parameters)) + { + return ResultCode.InvalidUpdateInfo; + } + + bool isMixContextDirty = false; + + for (int i = 0; i < parameters.Length; i++) + { + MixParameter parameter = parameters[i]; + + int mixId = i; + + if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()) + { + mixId = parameter.MixId; + } + + ref MixState mix = ref mixContext.GetState(mixId); + + if (parameter.IsUsed != mix.IsUsed) + { + mix.IsUsed = parameter.IsUsed; + + if (parameter.IsUsed) + { + mix.ClearEffectProcessingOrder(); + } + + isMixContextDirty = true; + } + + if (mix.IsUsed) + { + isMixContextDirty |= mix.Update(mixContext.EdgeMatrix, ref parameter, effectContext, splitterContext, _behaviourContext); + } + } + + if (isMixContextDirty) + { + if (_behaviourContext.IsSplitterSupported() && splitterContext.UsingSplitter()) + { + if (!mixContext.Sort(splitterContext)) + { + return ResultCode.InvalidMixSorting; + } + } + else + { + mixContext.Sort(); + } + } + + return ResultCode.Success; + } + + private static void ResetSink(ref BaseSink sink, ref SinkInParameter parameter) + { + sink.CleanUp(); + + switch (parameter.Type) + { + case SinkType.Invalid: + sink = new BaseSink(); + break; + case SinkType.CircularBuffer: + sink = new CircularBufferSink(); + break; + case SinkType.Device: + sink = new DeviceSink(); + break; + default: + throw new NotImplementedException($"SinkType {parameter.Type} not implemented!"); + } + } + + public ResultCode UpdateSinks(SinkContext context, Memory memoryPools) + { + PoolMapper mapper = new PoolMapper(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + + if (context.GetCount() * Unsafe.SizeOf() != _inputHeader.SinksSize) + { + return ResultCode.InvalidUpdateInfo; + } + + int initialOutputSize = _output.Length; + + ReadOnlySpan parameters = MemoryMarshal.Cast(_input.Slice(0, (int)_inputHeader.SinksSize).Span); + + _input = _input.Slice((int)_inputHeader.SinksSize); + + for (int i = 0; i < context.GetCount(); i++) + { + SinkInParameter parameter = parameters[i]; + ref SinkOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + ref BaseSink sink = ref context.GetSink(i); + + if (!sink.IsTypeValid(ref parameter)) + { + ResetSink(ref sink, ref parameter); + } + + sink.Update(out ErrorInfo updateErrorInfo, ref parameter, ref outStatus, mapper); + + if (updateErrorInfo.ErrorCode != ResultCode.Success) + { + _behaviourContext.AppendError(ref updateErrorInfo); + } + } + + int currentOutputSize = _output.Length; + + OutputHeader.SinksSize = (uint)(Unsafe.SizeOf() * context.GetCount()); + OutputHeader.TotalSize += OutputHeader.SinksSize; + + Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.SinksSize); + + return ResultCode.Success; + } + + public ResultCode UpdatePerformanceBuffer(PerformanceManager manager, Span performanceOutput) + { + if (Unsafe.SizeOf() != _inputHeader.PerformanceBufferSize) + { + return ResultCode.InvalidUpdateInfo; + } + + PerformanceInParameter parameter = SpanIOHelper.Read(ref _input); + + ref PerformanceOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + if (manager != null) + { + outStatus.HistorySize = manager.CopyHistories(performanceOutput); + + manager.SetTargetNodeId(parameter.TargetNodeId); + } + else + { + outStatus.HistorySize = 0; + } + + OutputHeader.PerformanceBufferSize = (uint)Unsafe.SizeOf(); + OutputHeader.TotalSize += OutputHeader.PerformanceBufferSize; + + return ResultCode.Success; + } + + public ResultCode UpdateErrorInfo() + { + ref BehaviourErrorInfoOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + _behaviourContext.CopyErrorInfo(outStatus.ErrorInfos.AsSpan(), out outStatus.ErrorInfosCount); + + OutputHeader.BehaviourSize = (uint)Unsafe.SizeOf(); + OutputHeader.TotalSize += OutputHeader.BehaviourSize; + + return ResultCode.Success; + } + + public ResultCode UpdateRendererInfo(ulong elapsedFrameCount) + { + ref RendererInfoOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; + + outStatus.ElapsedFrameCount = elapsedFrameCount; + + OutputHeader.RenderInfoSize = (uint)Unsafe.SizeOf(); + OutputHeader.TotalSize += OutputHeader.RenderInfoSize; + + return ResultCode.Success; + } + + public ResultCode CheckConsumedSize() + { + int consumedInputSize = _inputOrigin.Length - _input.Length; + int consumedOutputSize = _outputOrigin.Length - _output.Length; + + if (consumedInputSize != _inputHeader.TotalSize) + { + Logger.Error?.Print(LogClass.AudioRenderer, $"Consumed input size mismatch (got {consumedInputSize} expected {_inputHeader.TotalSize})"); + + return ResultCode.InvalidUpdateInfo; + } + + if (consumedOutputSize != OutputHeader.TotalSize) + { + Logger.Error?.Print(LogClass.AudioRenderer, $"Consumed output size mismatch (got {consumedOutputSize} expected {OutputHeader.TotalSize})"); + + return ResultCode.InvalidUpdateInfo; + } + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererExecutionMode.cs b/src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererExecutionMode.cs new file mode 100644 index 00000000..5d82ce0b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererExecutionMode.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.Audio.Renderer.Server.Types +{ + /// + /// The execution mode of an . + /// + public enum AudioRendererExecutionMode : byte + { + /// + /// Automatically send commands to the DSP at a fixed rate (see + /// + Auto, + + /// + /// Audio renderer operation needs to be done manually via ExecuteAudioRenderer. + /// + /// This is not supported on the DSP and is as such stubbed. + Manual + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererRenderingDevice.cs b/src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererRenderingDevice.cs new file mode 100644 index 00000000..5ad27b0b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Types/AudioRendererRenderingDevice.cs @@ -0,0 +1,24 @@ +namespace Ryujinx.Audio.Renderer.Server.Types +{ + /// + /// The rendering device of an . + /// + public enum AudioRendererRenderingDevice : byte + { + /// + /// Rendering is performed on the DSP. + /// + /// + /// Only supports . + /// + Dsp, + + /// + /// Rendering is performed on the CPU. + /// + /// + /// Only supports . + /// + Cpu + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Types/PlayState.cs b/src/Ryujinx.Audio/Renderer/Server/Types/PlayState.cs new file mode 100644 index 00000000..25cc34a8 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Types/PlayState.cs @@ -0,0 +1,39 @@ +namespace Ryujinx.Audio.Renderer.Server.Types +{ + /// + /// The internal play state of a + /// + public enum PlayState + { + /// + /// The voice has been started and is playing. + /// + Started, + + /// + /// The voice has been stopped. + /// + /// + /// This cannot be directly set by user. + /// See for correct usage. + /// + Stopped, + + /// + /// The user asked the voice to be stopped. + /// + /// + /// This is changed to the state after command generation. + /// + /// + Stopping, + + /// + /// The voice has been paused by user request. + /// + /// + /// The user can resume to the state. + /// + Paused + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerBufferState.cs b/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerBufferState.cs new file mode 100644 index 00000000..a45fa8e5 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerBufferState.cs @@ -0,0 +1,14 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Audio.Renderer.Server.Upsampler +{ + public struct UpsamplerBufferState + { + public const int HistoryLength = 20; + + public float Scale; + public Array20 History; + public bool Initialized; + public int Phase; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerManager.cs b/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerManager.cs new file mode 100644 index 00000000..b37988fe --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerManager.cs @@ -0,0 +1,84 @@ +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server.Upsampler +{ + /// + /// Upsampler manager. + /// + public class UpsamplerManager + { + /// + /// Work buffer for upsampler. + /// + private Memory _upSamplerWorkBuffer; + + /// + /// Global lock of the object. + /// + private object Lock = new object(); + + /// + /// The upsamplers instances. + /// + private UpsamplerState[] _upsamplers; + + /// + /// The count of upsamplers. + /// + private uint _count; + + /// + /// Create a new . + /// + /// Work buffer for upsampler. + /// The count of upsamplers. + public UpsamplerManager(Memory upSamplerWorkBuffer, uint count) + { + _upSamplerWorkBuffer = upSamplerWorkBuffer; + _count = count; + + _upsamplers = new UpsamplerState[_count]; + } + + /// + /// Allocate a new . + /// + /// A new or null if out of memory. + public UpsamplerState Allocate() + { + int workBufferOffset = 0; + + lock (Lock) + { + for (int i = 0; i < _count; i++) + { + if (_upsamplers[i] == null) + { + _upsamplers[i] = new UpsamplerState(this, i, _upSamplerWorkBuffer.Slice(workBufferOffset, Constants.UpSampleEntrySize), Constants.TargetSampleCount); + + return _upsamplers[i]; + } + + workBufferOffset += Constants.UpSampleEntrySize; + } + } + + return null; + } + + /// + /// Free a at the given index. + /// + /// The index of the to free. + public void Free(int index) + { + lock (Lock) + { + Debug.Assert(_upsamplers[index] != null); + + _upsamplers[index] = null; + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs b/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs new file mode 100644 index 00000000..e508f35b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs @@ -0,0 +1,68 @@ +using System; + +namespace Ryujinx.Audio.Renderer.Server.Upsampler +{ + /// + /// Server state for a upsampling. + /// + public class UpsamplerState + { + /// + /// The output buffer containing the target samples. + /// + public Memory OutputBuffer { get; } + + /// + /// The target sample count. + /// + public uint SampleCount { get; } + + /// + /// The index of the . (used to free it) + /// + private int _index; + + /// + /// The . + /// + private UpsamplerManager _manager; + + /// + /// The source sample count. + /// + public uint SourceSampleCount; + + /// + /// The input buffer indices of the buffers holding the samples that need upsampling. + /// + public ushort[] InputBufferIndices; + + /// + /// State of each input buffer index kept across invocations of the upsampler. + /// + public UpsamplerBufferState[] BufferStates; + + /// + /// Create a new . + /// + /// The upsampler manager. + /// The index of the . (used to free it) + /// The output buffer used to contain the target samples. + /// The target sample count. + public UpsamplerState(UpsamplerManager manager, int index, Memory outputBuffer, uint sampleCount) + { + _manager = manager; + _index = index; + OutputBuffer = outputBuffer; + SampleCount = sampleCount; + } + + /// + /// Release the . + /// + public void Release() + { + _manager.Free(_index); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceChannelResource.cs b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceChannelResource.cs new file mode 100644 index 00000000..939d9294 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceChannelResource.cs @@ -0,0 +1,40 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Voice +{ + /// + /// Server state for a voice channel resource. + /// + [StructLayout(LayoutKind.Sequential, Size = 0xD0, Pack = Alignment)] + public struct VoiceChannelResource + { + public const int Alignment = 0x10; + + /// + /// Mix volumes for the resource. + /// + public Array24 Mix; + + /// + /// Previous mix volumes for resource. + /// + public Array24 PreviousMix; + + /// + /// The id of the resource. + /// + public uint Id; + + /// + /// Indicate if the resource is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsUsed; + + public void UpdateState() + { + Mix.AsSpan().CopyTo(PreviousMix.AsSpan()); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceContext.cs b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceContext.cs new file mode 100644 index 00000000..1c57b71b --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceContext.cs @@ -0,0 +1,149 @@ +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Utils; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Renderer.Server.Voice +{ + /// + /// Voice context. + /// + public class VoiceContext + { + /// + /// Storage of the sorted indices to . + /// + private Memory _sortedVoices; + + /// + /// Storage for . + /// + private Memory _voices; + + /// + /// Storage for . + /// + private Memory _voiceChannelResources; + + /// + /// Storage for that are used during audio renderer server updates. + /// + private Memory _voiceUpdateStatesCpu; + + /// + /// Storage for for the . + /// + private Memory _voiceUpdateStatesDsp; + + /// + /// The total voice count. + /// + private uint _voiceCount; + + public void Initialize(Memory sortedVoices, Memory voices, Memory voiceChannelResources, Memory voiceUpdateStatesCpu, Memory voiceUpdateStatesDsp, uint voiceCount) + { + _sortedVoices = sortedVoices; + _voices = voices; + _voiceChannelResources = voiceChannelResources; + _voiceUpdateStatesCpu = voiceUpdateStatesCpu; + _voiceUpdateStatesDsp = voiceUpdateStatesDsp; + _voiceCount = voiceCount; + } + + /// + /// Get the total voice count. + /// + /// The total voice count. + public uint GetCount() + { + return _voiceCount; + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref VoiceChannelResource GetChannelResource(int id) + { + return ref SpanIOHelper.GetFromMemory(_voiceChannelResources, id, _voiceCount); + } + + /// + /// Get a at the given . + /// + /// The index to use. + /// A at the given . + /// The returned should only be used when updating the server state. + public Memory GetUpdateStateForCpu(int id) + { + return SpanIOHelper.GetMemory(_voiceUpdateStatesCpu, id, _voiceCount); + } + + /// + /// Get a at the given . + /// + /// The index to use. + /// A at the given . + /// The returned should only be used in the context of processing on the . + public Memory GetUpdateStateForDsp(int id) + { + return SpanIOHelper.GetMemory(_voiceUpdateStatesDsp, id, _voiceCount); + } + + /// + /// Get a reference to a at the given . + /// + /// The index to use. + /// A reference to a at the given . + public ref VoiceState GetState(int id) + { + return ref SpanIOHelper.GetFromMemory(_voices, id, _voiceCount); + } + + public ref VoiceState GetSortedState(int id) + { + Debug.Assert(id >= 0 && id < _voiceCount); + + return ref GetState(_sortedVoices.Span[id]); + } + + /// + /// Update internal state during command generation. + /// + public void UpdateForCommandGeneration() + { + _voiceUpdateStatesDsp.CopyTo(_voiceUpdateStatesCpu); + } + + /// + /// Sort the internal voices by priority and sorting order (if the priorities match). + /// + public void Sort() + { + for (int i = 0; i < _voiceCount; i++) + { + _sortedVoices.Span[i] = i; + } + + int[] sortedVoicesTemp = _sortedVoices.Slice(0, (int)GetCount()).ToArray(); + + Array.Sort(sortedVoicesTemp, (a, b) => + { + ref VoiceState aState = ref GetState(a); + ref VoiceState bState = ref GetState(b); + + int result = aState.Priority.CompareTo(bState.Priority); + + if (result == 0) + { + return aState.SortingOrder.CompareTo(bState.SortingOrder); + } + + return result; + }); + + sortedVoicesTemp.AsSpan().CopyTo(_sortedVoices.Span); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs new file mode 100644 index 00000000..0bf53c54 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs @@ -0,0 +1,699 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Renderer.Common; +using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; +using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; + +namespace Ryujinx.Audio.Renderer.Server.Voice +{ + [StructLayout(LayoutKind.Sequential, Pack = Alignment)] + public struct VoiceState + { + public const int Alignment = 0x10; + + /// + /// Set to true if the voice is used. + /// + [MarshalAs(UnmanagedType.I1)] + public bool InUse; + + /// + /// Set to true if the voice is new. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsNew; + + [MarshalAs(UnmanagedType.I1)] + public bool WasPlaying; + + /// + /// The of the voice. + /// + public SampleFormat SampleFormat; + + /// + /// The sample rate of the voice. + /// + public uint SampleRate; + + /// + /// The total channel count used. + /// + public uint ChannelsCount; + + /// + /// Id of the voice. + /// + public int Id; + + /// + /// Node id of the voice. + /// + public int NodeId; + + /// + /// The target mix id of the voice. + /// + public int MixId; + + /// + /// The current voice . + /// + public Types.PlayState PlayState; + + /// + /// The previous voice . + /// + public Types.PlayState PreviousPlayState; + + /// + /// The priority of the voice. + /// + public uint Priority; + + /// + /// Target sorting position of the voice. (used to sort voice with the same ) + /// + public uint SortingOrder; + + /// + /// The pitch used on the voice. + /// + public float Pitch; + + /// + /// The output volume of the voice. + /// + public float Volume; + + /// + /// The previous output volume of the voice. + /// + public float PreviousVolume; + + /// + /// Biquad filters to apply to the output of the voice. + /// + public Array2 BiquadFilters; + + /// + /// Total count of of the voice. + /// + public uint WaveBuffersCount; + + /// + /// Current playing of the voice. + /// + public uint WaveBuffersIndex; + + /// + /// Change the behaviour of the voice. + /// + /// This was added on REV5. + public DecodingBehaviour DecodingBehaviour; + + /// + /// User state required by the data source. + /// + /// Only used for as the GC-ADPCM coefficients. + public AddressInfo DataSourceStateAddressInfo; + + /// + /// The wavebuffers of this voice. + /// + public Array4 WaveBuffers; + + /// + /// The channel resource ids associated to the voice. + /// + public Array6 ChannelResourceIds; + + /// + /// The target splitter id of the voice. + /// + public uint SplitterId; + + /// + /// Change the Sample Rate Conversion (SRC) quality of the voice. + /// + /// This was added on REV8. + public SampleRateConversionQuality SrcQuality; + + /// + /// If set to true, the voice was dropped. + /// + [MarshalAs(UnmanagedType.I1)] + public bool VoiceDropFlag; + + /// + /// Set to true if the data source state work buffer wasn't mapped. + /// + [MarshalAs(UnmanagedType.I1)] + public bool DataSourceStateUnmapped; + + /// + /// Set to true if any of the work buffer wasn't mapped. + /// + [MarshalAs(UnmanagedType.I1)] + public bool BufferInfoUnmapped; + + /// + /// The biquad filter initialization state storage. + /// + private BiquadFilterNeedInitializationArrayStruct _biquadFilterNeedInitialization; + + /// + /// Flush the amount of wavebuffer specified. This will result in the wavebuffer being skipped and marked played. + /// + /// This was added on REV5. + public byte FlushWaveBufferCount; + + [StructLayout(LayoutKind.Sequential, Size = Constants.VoiceBiquadFilterCount)] + private struct BiquadFilterNeedInitializationArrayStruct { } + + /// + /// The biquad filter initialization state array. + /// + public Span BiquadFilterNeedInitialization => SpanHelpers.AsSpan(ref _biquadFilterNeedInitialization); + + /// + /// Initialize the . + /// + public void Initialize() + { + IsNew = false; + VoiceDropFlag = false; + DataSourceStateUnmapped = false; + BufferInfoUnmapped = false; + FlushWaveBufferCount = 0; + PlayState = Types.PlayState.Stopped; + Priority = Constants.VoiceLowestPriority; + Id = 0; + NodeId = 0; + SampleRate = 0; + SampleFormat = SampleFormat.Invalid; + ChannelsCount = 0; + Pitch = 0.0f; + Volume = 0.0f; + PreviousVolume = 0.0f; + BiquadFilters.AsSpan().Fill(new BiquadFilterParameter()); + WaveBuffersCount = 0; + WaveBuffersIndex = 0; + MixId = Constants.UnusedMixId; + SplitterId = Constants.UnusedSplitterId; + DataSourceStateAddressInfo.Setup(0, 0); + + InitializeWaveBuffers(); + } + + /// + /// Initialize the in this . + /// + private void InitializeWaveBuffers() + { + for (int i = 0; i < WaveBuffers.Length; i++) + { + WaveBuffers[i].StartSampleOffset = 0; + WaveBuffers[i].EndSampleOffset = 0; + WaveBuffers[i].ShouldLoop = false; + WaveBuffers[i].IsEndOfStream = false; + WaveBuffers[i].BufferAddressInfo.Setup(0, 0); + WaveBuffers[i].ContextAddressInfo.Setup(0, 0); + WaveBuffers[i].IsSendToAudioProcessor = true; + } + } + + /// + /// Check if the voice needs to be skipped. + /// + /// Returns true if the voice needs to be skipped. + public bool ShouldSkip() + { + return !InUse || WaveBuffersCount == 0 || DataSourceStateUnmapped || BufferInfoUnmapped || VoiceDropFlag; + } + + /// + /// Return true if the mix has any destinations. + /// + /// True if the mix has any destinations. + public bool HasAnyDestination() + { + return MixId != Constants.UnusedMixId || SplitterId != Constants.UnusedSplitterId; + } + + /// + /// Indicate if the server voice information needs to be updated. + /// + /// The user parameter. + /// Return true, if the server voice information needs to be updated. + private bool ShouldUpdateParameters(ref VoiceInParameter parameter) + { + if (DataSourceStateAddressInfo.CpuAddress == parameter.DataSourceStateAddress) + { + return DataSourceStateAddressInfo.Size != parameter.DataSourceStateSize; + } + + return DataSourceStateAddressInfo.CpuAddress != parameter.DataSourceStateAddress || + DataSourceStateAddressInfo.Size != parameter.DataSourceStateSize || + DataSourceStateUnmapped; + } + + /// + /// Update the internal state from a user parameter. + /// + /// The possible that was generated. + /// The user parameter. + /// The mapper to use. + /// The behaviour context. + public void UpdateParameters(out ErrorInfo outErrorInfo, ref VoiceInParameter parameter, ref PoolMapper poolMapper, ref BehaviourContext behaviourContext) + { + InUse = parameter.InUse; + Id = parameter.Id; + NodeId = parameter.NodeId; + + UpdatePlayState(parameter.PlayState); + + SrcQuality = parameter.SrcQuality; + + Priority = parameter.Priority; + SortingOrder = parameter.SortingOrder; + SampleRate = parameter.SampleRate; + SampleFormat = parameter.SampleFormat; + ChannelsCount = parameter.ChannelCount; + Pitch = parameter.Pitch; + Volume = parameter.Volume; + parameter.BiquadFilters.AsSpan().CopyTo(BiquadFilters.AsSpan()); + WaveBuffersCount = parameter.WaveBuffersCount; + WaveBuffersIndex = parameter.WaveBuffersIndex; + + if (behaviourContext.IsFlushVoiceWaveBuffersSupported()) + { + FlushWaveBufferCount += parameter.FlushWaveBufferCount; + } + + MixId = parameter.MixId; + + if (behaviourContext.IsSplitterSupported()) + { + SplitterId = parameter.SplitterId; + } + else + { + SplitterId = Constants.UnusedSplitterId; + } + + parameter.ChannelResourceIds.AsSpan().CopyTo(ChannelResourceIds.AsSpan()); + + DecodingBehaviour behaviour = DecodingBehaviour.Default; + + if (behaviourContext.IsDecodingBehaviourFlagSupported()) + { + behaviour = parameter.DecodingBehaviourFlags; + } + + DecodingBehaviour = behaviour; + + if (parameter.ResetVoiceDropFlag) + { + VoiceDropFlag = false; + } + + if (ShouldUpdateParameters(ref parameter)) + { + DataSourceStateUnmapped = !poolMapper.TryAttachBuffer(out outErrorInfo, ref DataSourceStateAddressInfo, parameter.DataSourceStateAddress, parameter.DataSourceStateSize); + } + else + { + outErrorInfo = new ErrorInfo(); + } + } + + /// + /// Update the internal play state from user play state. + /// + /// The target user play state. + public void UpdatePlayState(PlayState userPlayState) + { + Types.PlayState oldServerPlayState = PlayState; + + PreviousPlayState = oldServerPlayState; + + Types.PlayState newServerPlayState; + + switch (userPlayState) + { + case Common.PlayState.Start: + newServerPlayState = Types.PlayState.Started; + break; + + case Common.PlayState.Stop: + if (oldServerPlayState == Types.PlayState.Stopped) + { + return; + } + + newServerPlayState = Types.PlayState.Stopping; + break; + + case Common.PlayState.Pause: + newServerPlayState = Types.PlayState.Paused; + break; + + default: + throw new NotImplementedException($"Unhandled PlayState.{userPlayState}"); + } + + PlayState = newServerPlayState; + } + + /// + /// Write the status of the voice to the given user output. + /// + /// The given user output. + /// The user parameter. + /// The voice states associated to the . + public void WriteOutStatus(ref VoiceOutStatus outStatus, ref VoiceInParameter parameter, ReadOnlySpan> voiceUpdateStates) + { +#if DEBUG + // Sanity check in debug mode of the internal state + if (!parameter.IsNew && !IsNew) + { + for (int i = 1; i < ChannelsCount; i++) + { + ref VoiceUpdateState stateA = ref voiceUpdateStates[i - 1].Span[0]; + ref VoiceUpdateState stateB = ref voiceUpdateStates[i].Span[0]; + + Debug.Assert(stateA.WaveBufferConsumed == stateB.WaveBufferConsumed); + Debug.Assert(stateA.PlayedSampleCount == stateB.PlayedSampleCount); + Debug.Assert(stateA.Offset == stateB.Offset); + Debug.Assert(stateA.WaveBufferIndex == stateB.WaveBufferIndex); + Debug.Assert(stateA.Fraction == stateB.Fraction); + Debug.Assert(stateA.IsWaveBufferValid.SequenceEqual(stateB.IsWaveBufferValid)); + } + } +#endif + if (parameter.IsNew || IsNew) + { + IsNew = true; + + outStatus.VoiceDropFlag = false; + outStatus.PlayedWaveBuffersCount = 0; + outStatus.PlayedSampleCount = 0; + } + else + { + ref VoiceUpdateState state = ref voiceUpdateStates[0].Span[0]; + + outStatus.VoiceDropFlag = VoiceDropFlag; + outStatus.PlayedWaveBuffersCount = state.WaveBufferConsumed; + outStatus.PlayedSampleCount = state.PlayedSampleCount; + } + } + + /// + /// Update the internal state of all the of the . + /// + /// An array of used to report errors when mapping any of the . + /// The user parameter. + /// The voice states associated to the . + /// The mapper to use. + /// The behaviour context. + public void UpdateWaveBuffers(out ErrorInfo[] errorInfos, ref VoiceInParameter parameter, ReadOnlySpan> voiceUpdateStates, ref PoolMapper mapper, ref BehaviourContext behaviourContext) + { + errorInfos = new ErrorInfo[Constants.VoiceWaveBufferCount * 2]; + + if (parameter.IsNew) + { + InitializeWaveBuffers(); + + for (int i = 0; i < parameter.ChannelCount; i++) + { + voiceUpdateStates[i].Span[0].IsWaveBufferValid.Fill(false); + } + } + + ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[0].Span[0]; + + for (int i = 0; i < Constants.VoiceWaveBufferCount; i++) + { + UpdateWaveBuffer(errorInfos.AsSpan(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], ref mapper, ref behaviourContext); + } + } + + /// + /// Update the internal state of one of the of the . + /// + /// A used to report errors when mapping the . + /// The to update. + /// The from the user input. + /// The from the user input. + /// If set to true, the server side wavebuffer is considered valid. + /// The mapper to use. + /// The behaviour context. + private void UpdateWaveBuffer(Span errorInfos, ref WaveBuffer waveBuffer, ref WaveBufferInternal inputWaveBuffer, SampleFormat sampleFormat, bool isValid, ref PoolMapper mapper, ref BehaviourContext behaviourContext) + { + if (!isValid && waveBuffer.IsSendToAudioProcessor && waveBuffer.BufferAddressInfo.CpuAddress != 0) + { + mapper.ForceUnmap(ref waveBuffer.BufferAddressInfo); + waveBuffer.BufferAddressInfo.Setup(0, 0); + } + + if (!inputWaveBuffer.SentToServer || BufferInfoUnmapped) + { + if (inputWaveBuffer.IsSampleOffsetValid(sampleFormat)) + { + Debug.Assert(waveBuffer.IsSendToAudioProcessor); + + waveBuffer.IsSendToAudioProcessor = false; + waveBuffer.StartSampleOffset = inputWaveBuffer.StartSampleOffset; + waveBuffer.EndSampleOffset = inputWaveBuffer.EndSampleOffset; + waveBuffer.ShouldLoop = inputWaveBuffer.ShouldLoop; + waveBuffer.IsEndOfStream = inputWaveBuffer.IsEndOfStream; + waveBuffer.LoopStartSampleOffset = inputWaveBuffer.LoopFirstSampleOffset; + waveBuffer.LoopEndSampleOffset = inputWaveBuffer.LoopLastSampleOffset; + waveBuffer.LoopCount = inputWaveBuffer.LoopCount; + + BufferInfoUnmapped = !mapper.TryAttachBuffer(out ErrorInfo bufferInfoError, ref waveBuffer.BufferAddressInfo, inputWaveBuffer.Address, inputWaveBuffer.Size); + + errorInfos[0] = bufferInfoError; + + if (sampleFormat == SampleFormat.Adpcm && behaviourContext.IsAdpcmLoopContextBugFixed() && inputWaveBuffer.ContextAddress != 0) + { + bool adpcmLoopContextMapped = mapper.TryAttachBuffer(out ErrorInfo adpcmLoopContextInfoError, + ref waveBuffer.ContextAddressInfo, + inputWaveBuffer.ContextAddress, + inputWaveBuffer.ContextSize); + + errorInfos[1] = adpcmLoopContextInfoError; + + if (adpcmLoopContextMapped) + { + BufferInfoUnmapped = DataSourceStateUnmapped; + } + else + { + BufferInfoUnmapped = true; + } + } + else + { + waveBuffer.ContextAddressInfo.Setup(0, 0); + } + } + else + { + errorInfos[0].ErrorCode = ResultCode.InvalidAddressInfo; + errorInfos[0].ExtraErrorInfo = inputWaveBuffer.Address; + } + } + } + + /// + /// Reset the resources associated to this . + /// + /// The voice context. + private void ResetResources(VoiceContext context) + { + for (int i = 0; i < ChannelsCount; i++) + { + int channelResourceId = ChannelResourceIds[i]; + + ref VoiceChannelResource voiceChannelResource = ref context.GetChannelResource(channelResourceId); + + Debug.Assert(voiceChannelResource.IsUsed); + + Memory dspSharedState = context.GetUpdateStateForDsp(channelResourceId); + + MemoryMarshal.Cast(dspSharedState.Span).Fill(0); + + voiceChannelResource.UpdateState(); + } + } + + /// + /// Flush a certain amount of . + /// + /// The amount of wavebuffer to flush. + /// The voice states associated to the . + /// The channel count from user input. + private void FlushWaveBuffers(uint waveBufferCount, Memory[] voiceUpdateStates, uint channelCount) + { + uint waveBufferIndex = WaveBuffersIndex; + + for (int i = 0; i < waveBufferCount; i++) + { + WaveBuffers[(int)waveBufferIndex].IsSendToAudioProcessor = true; + + for (int j = 0; j < channelCount; j++) + { + ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[j].Span[0]; + + voiceUpdateState.WaveBufferIndex = (voiceUpdateState.WaveBufferIndex + 1) % Constants.VoiceWaveBufferCount; + voiceUpdateState.WaveBufferConsumed++; + voiceUpdateState.IsWaveBufferValid[(int)waveBufferIndex] = false; + } + + waveBufferIndex = (waveBufferIndex + 1) % Constants.VoiceWaveBufferCount; + } + } + + /// + /// Update the internal parameters for command generation. + /// + /// The voice states associated to the . + /// Return true if this voice should be played. + public bool UpdateParametersForCommandGeneration(Memory[] voiceUpdateStates) + { + if (FlushWaveBufferCount != 0) + { + FlushWaveBuffers(FlushWaveBufferCount, voiceUpdateStates, ChannelsCount); + + FlushWaveBufferCount = 0; + } + + switch (PlayState) + { + case Types.PlayState.Started: + for (int i = 0; i < WaveBuffers.Length; i++) + { + ref WaveBuffer wavebuffer = ref WaveBuffers[i]; + + if (!wavebuffer.IsSendToAudioProcessor) + { + for (int y = 0; y < ChannelsCount; y++) + { + Debug.Assert(!voiceUpdateStates[y].Span[0].IsWaveBufferValid[i]); + + voiceUpdateStates[y].Span[0].IsWaveBufferValid[i] = true; + } + + wavebuffer.IsSendToAudioProcessor = true; + } + } + + WasPlaying = false; + + ref VoiceUpdateState primaryVoiceUpdateState = ref voiceUpdateStates[0].Span[0]; + + for (int i = 0; i < primaryVoiceUpdateState.IsWaveBufferValid.Length; i++) + { + if (primaryVoiceUpdateState.IsWaveBufferValid[i]) + { + return true; + } + } + + return false; + + case Types.PlayState.Stopping: + for (int i = 0; i < WaveBuffers.Length; i++) + { + ref WaveBuffer wavebuffer = ref WaveBuffers[i]; + + wavebuffer.IsSendToAudioProcessor = true; + + for (int j = 0; j < ChannelsCount; j++) + { + ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[j].Span[0]; + + if (voiceUpdateState.IsWaveBufferValid[i]) + { + voiceUpdateState.WaveBufferIndex = (voiceUpdateState.WaveBufferIndex + 1) % Constants.VoiceWaveBufferCount; + voiceUpdateState.WaveBufferConsumed++; + } + + voiceUpdateState.IsWaveBufferValid[i] = false; + } + } + + for (int i = 0; i < ChannelsCount; i++) + { + ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[i].Span[0]; + + voiceUpdateState.Offset = 0; + voiceUpdateState.PlayedSampleCount = 0; + voiceUpdateState.Pitch.AsSpan().Fill(0); + voiceUpdateState.Fraction = 0; + voiceUpdateState.LoopContext = new Dsp.State.AdpcmLoopContext(); + } + + PlayState = Types.PlayState.Stopped; + WasPlaying = PreviousPlayState == Types.PlayState.Started; + + return WasPlaying; + + case Types.PlayState.Stopped: + case Types.PlayState.Paused: + foreach (ref WaveBuffer wavebuffer in WaveBuffers.AsSpan()) + { + wavebuffer.BufferAddressInfo.GetReference(true); + wavebuffer.ContextAddressInfo.GetReference(true); + } + + if (SampleFormat == SampleFormat.Adpcm) + { + if (DataSourceStateAddressInfo.CpuAddress != 0) + { + DataSourceStateAddressInfo.GetReference(true); + } + } + + WasPlaying = PreviousPlayState == Types.PlayState.Started; + + return WasPlaying; + default: + throw new NotImplementedException($"{PlayState}"); + } + } + + /// + /// Update the internal state for command generation. + /// + /// The voice context. + /// Return true if this voice should be played. + public bool UpdateForCommandGeneration(VoiceContext context) + { + if (IsNew) + { + ResetResources(context); + PreviousVolume = Volume; + IsNew = false; + } + + Memory[] voiceUpdateStates = new Memory[Constants.VoiceChannelCountMax]; + + for (int i = 0; i < ChannelsCount; i++) + { + voiceUpdateStates[i] = context.GetUpdateStateForDsp(ChannelResourceIds[i]); + } + + return UpdateParametersForCommandGeneration(voiceUpdateStates); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Server/Voice/WaveBuffer.cs b/src/Ryujinx.Audio/Renderer/Server/Voice/WaveBuffer.cs new file mode 100644 index 00000000..4bf7dd28 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Server/Voice/WaveBuffer.cs @@ -0,0 +1,104 @@ +using Ryujinx.Audio.Renderer.Server.MemoryPool; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Server.Voice +{ + /// + /// A wavebuffer used for server update. + /// + [StructLayout(LayoutKind.Sequential, Size = 0x58, Pack = 1)] + public struct WaveBuffer + { + /// + /// The of the sample data of the wavebuffer. + /// + public AddressInfo BufferAddressInfo; + + /// + /// The of the context of the wavebuffer. + /// + /// Only used by . + public AddressInfo ContextAddressInfo; + + + /// + /// First sample to play of the wavebuffer. + /// + public uint StartSampleOffset; + + /// + /// Last sample to play of the wavebuffer. + /// + public uint EndSampleOffset; + + /// + /// Set to true if the wavebuffer is looping. + /// + [MarshalAs(UnmanagedType.I1)] + public bool ShouldLoop; + + /// + /// Set to true if the wavebuffer is the end of stream. + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsEndOfStream; + + /// + /// Set to true if the wavebuffer wasn't sent to the . + /// + [MarshalAs(UnmanagedType.I1)] + public bool IsSendToAudioProcessor; + + /// + /// First sample to play when looping the wavebuffer. + /// + public uint LoopStartSampleOffset; + + /// + /// Last sample to play when looping the wavebuffer. + /// + public uint LoopEndSampleOffset; + + /// + /// The max loop count. + /// + public int LoopCount; + + /// + /// Create a new for use by the . + /// + /// The target version of the wavebuffer. + /// A new for use by the . + public Common.WaveBuffer ToCommon(int version) + { + Common.WaveBuffer waveBuffer = new Common.WaveBuffer(); + + waveBuffer.Buffer = BufferAddressInfo.GetReference(true); + waveBuffer.BufferSize = (uint)BufferAddressInfo.Size; + + if (ContextAddressInfo.CpuAddress != 0) + { + waveBuffer.Context = ContextAddressInfo.GetReference(true); + waveBuffer.ContextSize = (uint)ContextAddressInfo.Size; + } + + waveBuffer.StartSampleOffset = StartSampleOffset; + waveBuffer.EndSampleOffset = EndSampleOffset; + waveBuffer.Looping = ShouldLoop; + waveBuffer.IsEndOfStream = IsEndOfStream; + + if (version == 2) + { + waveBuffer.LoopCount = LoopCount; + waveBuffer.LoopStartSampleOffset = LoopStartSampleOffset; + waveBuffer.LoopEndSampleOffset = LoopEndSampleOffset; + } + else + { + waveBuffer.LoopCount = -1; + } + + return waveBuffer; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Utils/AudioProcessorMemoryManager.cs b/src/Ryujinx.Audio/Renderer/Utils/AudioProcessorMemoryManager.cs new file mode 100644 index 00000000..973f0672 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/AudioProcessorMemoryManager.cs @@ -0,0 +1,57 @@ +using System.Runtime.CompilerServices; +using CpuAddress = System.UInt64; +using DspAddress = System.UInt64; + +namespace Ryujinx.Audio.Renderer.Utils +{ + /// + /// The memory management + /// + /// This is stub for the most part but is kept to permit LLE if wanted. + static class AudioProcessorMemoryManager + { + /// + /// Map the given to the address space. + /// + /// The process owning the CPU memory. + /// The to map. + /// The size of the CPU memory region to map. + /// The address on the address space. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static DspAddress Map(uint processHandle, CpuAddress cpuAddress, ulong size) + { + return cpuAddress; + } + + /// + /// Unmap the given from the address space. + /// + /// The process owning the CPU memory. + /// The to unmap. + /// The size of the CPU memory region to unmap. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Unmap(uint processHandle, CpuAddress cpuAddress, ulong size) + { + } + + /// + /// Invalidate the data cache at the given address. + /// + /// The base DSP address to invalidate + /// The size of the DSP memory region to invalidate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvalidateDspCache(DspAddress address, ulong size) + { + } + + /// + /// Invalidate the CPU data cache at the given address. + /// + /// The base to invalidate + /// The size of the CPU memory region to invalidate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvalidateDataCache(CpuAddress address, ulong size) + { + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Utils/BitArray.cs b/src/Ryujinx.Audio/Renderer/Utils/BitArray.cs new file mode 100644 index 00000000..8b105477 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/BitArray.cs @@ -0,0 +1,103 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Utils +{ + /// + /// A simple bit array implementation backed by a . + /// + public class BitArray + { + /// + /// The backing storage of the . + /// + private Memory _storage; + + /// + /// Create a new from . + /// + /// The backing storage of the . + public BitArray(Memory storage) + { + _storage = storage; + } + + /// + /// Get the byte position of a given bit index. + /// + /// A bit index. + /// The byte position of a given bit index. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ToPosition(int index) => index / 8; + + /// + /// Get the bit position of a given bit index inside a byte. + /// + /// A bit index. + /// The bit position of a given bit index inside a byte. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ToBitPosition(int index) => index % 8; + + /// + /// Test if the bit at the given index is set. + /// + /// A bit index. + /// Return true if the bit at the given index is set + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Test(int index) + { + ulong mask = 1ul << ToBitPosition(index); + + return (_storage.Span[ToPosition(index)] & mask) == mask; + } + + /// + /// Set the bit at the given index. + /// + /// A bit index. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Set(int index) + { + Set(index, true); + } + + /// + /// Reset the bit at the given index. + /// + /// A bit index. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Reset(int index) + { + Set(index, false); + } + + /// + /// Set a bit value at the given index. + /// + /// A bit index. + /// The new bit value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Set(int index, bool value) + { + byte mask = (byte)(1 << ToBitPosition(index)); + + if (value) + { + _storage.Span[ToPosition(index)] |= mask; + } + else + { + _storage.Span[ToPosition(index)] &= (byte)~mask; + } + } + + /// + /// Reset all bits in the storage. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Reset() + { + _storage.Span.Fill(0); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs b/src/Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs new file mode 100644 index 00000000..d49313ea --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs @@ -0,0 +1,99 @@ +using Ryujinx.Audio.Integration; +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Audio.Renderer.Utils +{ + /// + /// A that outputs to a wav file. + /// + public class FileHardwareDevice : IHardwareDevice + { + private FileStream _stream; + private uint _channelCount; + private uint _sampleRate; + + private const int HeaderSize = 44; + + public FileHardwareDevice(string path, uint channelCount, uint sampleRate) + { + _stream = File.OpenWrite(path); + _channelCount = channelCount; + _sampleRate = sampleRate; + + _stream.Seek(HeaderSize, SeekOrigin.Begin); + } + + private void UpdateHeader() + { + var writer = new BinaryWriter(_stream); + + long currentPos = writer.Seek(0, SeekOrigin.Current); + + writer.Seek(0, SeekOrigin.Begin); + + writer.Write("RIFF"u8); + writer.Write((int)(writer.BaseStream.Length - 8)); + writer.Write("WAVE"u8); + writer.Write("fmt "u8); + writer.Write(16); + writer.Write((short)1); + writer.Write((short)GetChannelCount()); + writer.Write(GetSampleRate()); + writer.Write(GetSampleRate() * GetChannelCount() * sizeof(short)); + writer.Write((short)(GetChannelCount() * sizeof(short))); + writer.Write((short)(sizeof(short) * 8)); + writer.Write("data"u8); + writer.Write((int)(writer.BaseStream.Length - HeaderSize)); + + writer.Seek((int)currentPos, SeekOrigin.Begin); + } + + public void AppendBuffer(ReadOnlySpan data, uint channelCount) + { + _stream.Write(MemoryMarshal.Cast(data)); + + UpdateHeader(); + _stream.Flush(); + } + + public void SetVolume(float volume) + { + // Do nothing, volume is not used for FileHardwareDevice at the moment. + } + + public float GetVolume() + { + // FileHardwareDevice does not incorporate volume. + return 0; + } + + public uint GetChannelCount() + { + return _channelCount; + } + + public uint GetSampleRate() + { + return _sampleRate; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _stream?.Flush(); + _stream?.Dispose(); + + _stream = null; + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Utils/Mailbox.cs b/src/Ryujinx.Audio/Renderer/Utils/Mailbox.cs new file mode 100644 index 00000000..35c71ae3 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/Mailbox.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Concurrent; + +namespace Ryujinx.Audio.Renderer.Utils +{ + /// + /// A simple generic message queue for unmanaged types. + /// + /// The target unmanaged type used + public class Mailbox : IDisposable where T : unmanaged + { + private BlockingCollection _messageQueue; + private BlockingCollection _responseQueue; + + public Mailbox() + { + _messageQueue = new BlockingCollection(1); + _responseQueue = new BlockingCollection(1); + } + + public void SendMessage(T data) + { + _messageQueue.Add(data); + } + + public void SendResponse(T data) + { + _responseQueue.Add(data); + } + + public T ReceiveMessage() + { + return _messageQueue.Take(); + } + + public T ReceiveResponse() + { + return _responseQueue.Take(); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _messageQueue.Dispose(); + _responseQueue.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Utils/Math/Matrix2x2.cs b/src/Ryujinx.Audio/Renderer/Utils/Math/Matrix2x2.cs new file mode 100644 index 00000000..5b513aff --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/Math/Matrix2x2.cs @@ -0,0 +1,71 @@ +namespace Ryujinx.Audio.Renderer.Utils.Math +{ + record struct Matrix2x2 + { + public float M11; + public float M12; + public float M21; + public float M22; + + public Matrix2x2(float m11, float m12, + float m21, float m22) + { + M11 = m11; + M12 = m12; + + M21 = m21; + M22 = m22; + } + + public static Matrix2x2 operator +(Matrix2x2 value1, Matrix2x2 value2) + { + Matrix2x2 m; + + m.M11 = value1.M11 + value2.M11; + m.M12 = value1.M12 + value2.M12; + m.M21 = value1.M21 + value2.M21; + m.M22 = value1.M22 + value2.M22; + + return m; + } + + public static Matrix2x2 operator -(Matrix2x2 value1, float value2) + { + Matrix2x2 m; + + m.M11 = value1.M11 - value2; + m.M12 = value1.M12 - value2; + m.M21 = value1.M21 - value2; + m.M22 = value1.M22 - value2; + + return m; + } + + public static Matrix2x2 operator *(Matrix2x2 value1, float value2) + { + Matrix2x2 m; + + m.M11 = value1.M11 * value2; + m.M12 = value1.M12 * value2; + m.M21 = value1.M21 * value2; + m.M22 = value1.M22 * value2; + + return m; + } + + public static Matrix2x2 operator *(Matrix2x2 value1, Matrix2x2 value2) + { + Matrix2x2 m; + + // First row + m.M11 = value1.M11 * value2.M11 + value1.M12 * value2.M21; + m.M12 = value1.M11 * value2.M12 + value1.M12 * value2.M22; + + // Second row + m.M21 = value1.M21 * value2.M11 + value1.M22 * value2.M21; + m.M22 = value1.M21 * value2.M12 + value1.M22 * value2.M22; + + return m; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Utils/Math/Matrix6x6.cs b/src/Ryujinx.Audio/Renderer/Utils/Math/Matrix6x6.cs new file mode 100644 index 00000000..415a81fd --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/Math/Matrix6x6.cs @@ -0,0 +1,97 @@ +namespace Ryujinx.Audio.Renderer.Utils.Math +{ + record struct Matrix6x6 + { + public float M11; + public float M12; + public float M13; + public float M14; + public float M15; + public float M16; + + public float M21; + public float M22; + public float M23; + public float M24; + public float M25; + public float M26; + + public float M31; + public float M32; + public float M33; + public float M34; + public float M35; + public float M36; + + public float M41; + public float M42; + public float M43; + public float M44; + public float M45; + public float M46; + + public float M51; + public float M52; + public float M53; + public float M54; + public float M55; + public float M56; + + public float M61; + public float M62; + public float M63; + public float M64; + public float M65; + public float M66; + + public Matrix6x6(float m11, float m12, float m13, float m14, float m15, float m16, + float m21, float m22, float m23, float m24, float m25, float m26, + float m31, float m32, float m33, float m34, float m35, float m36, + float m41, float m42, float m43, float m44, float m45, float m46, + float m51, float m52, float m53, float m54, float m55, float m56, + float m61, float m62, float m63, float m64, float m65, float m66) + { + M11 = m11; + M12 = m12; + M13 = m13; + M14 = m14; + M15 = m15; + M16 = m16; + + M21 = m21; + M22 = m22; + M23 = m23; + M24 = m24; + M25 = m25; + M26 = m26; + + M31 = m31; + M32 = m32; + M33 = m33; + M34 = m34; + M35 = m35; + M36 = m36; + + M41 = m41; + M42 = m42; + M43 = m43; + M44 = m44; + M45 = m45; + M46 = m46; + + M51 = m51; + M52 = m52; + M53 = m53; + M54 = m54; + M55 = m55; + M56 = m56; + + M61 = m61; + M62 = m62; + M63 = m63; + M64 = m64; + M65 = m65; + M66 = m66; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Utils/Math/MatrixHelper.cs b/src/Ryujinx.Audio/Renderer/Utils/Math/MatrixHelper.cs new file mode 100644 index 00000000..209a81c4 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/Math/MatrixHelper.cs @@ -0,0 +1,45 @@ +using Ryujinx.Audio.Renderer.Utils.Math; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Dsp +{ + static class MatrixHelper + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector6 Transform(ref Vector6 value1, ref Matrix6x6 value2) + { + return new Vector6 + { + X = value2.M11 * value1.X + value2.M12 * value1.Y + value2.M13 * value1.Z + value2.M14 * value1.W + value2.M15 * value1.V + value2.M16 * value1.U, + Y = value2.M21 * value1.X + value2.M22 * value1.Y + value2.M23 * value1.Z + value2.M24 * value1.W + value2.M25 * value1.V + value2.M26 * value1.U, + Z = value2.M31 * value1.X + value2.M32 * value1.Y + value2.M33 * value1.Z + value2.M34 * value1.W + value2.M35 * value1.V + value2.M36 * value1.U, + W = value2.M41 * value1.X + value2.M42 * value1.Y + value2.M43 * value1.Z + value2.M44 * value1.W + value2.M45 * value1.V + value2.M46 * value1.U, + V = value2.M51 * value1.X + value2.M52 * value1.Y + value2.M53 * value1.Z + value2.M54 * value1.W + value2.M55 * value1.V + value2.M56 * value1.U, + U = value2.M61 * value1.X + value2.M62 * value1.Y + value2.M63 * value1.Z + value2.M64 * value1.W + value2.M65 * value1.V + value2.M66 * value1.U, + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 Transform(ref Vector4 value1, ref Matrix4x4 value2) + { + return new Vector4 + { + X = value2.M11 * value1.X + value2.M12 * value1.Y + value2.M13 * value1.Z + value2.M14 * value1.W, + Y = value2.M21 * value1.X + value2.M22 * value1.Y + value2.M23 * value1.Z + value2.M24 * value1.W, + Z = value2.M31 * value1.X + value2.M32 * value1.Y + value2.M33 * value1.Z + value2.M34 * value1.W, + W = value2.M41 * value1.X + value2.M42 * value1.Y + value2.M43 * value1.Z + value2.M44 * value1.W + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2 Transform(ref Vector2 value1, ref Matrix2x2 value2) + { + return new Vector2 + { + X = value2.M11 * value1.X + value2.M12 * value1.Y, + Y = value2.M21 * value1.X + value2.M22 * value1.Y, + }; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Utils/Math/Vector6.cs b/src/Ryujinx.Audio/Renderer/Utils/Math/Vector6.cs new file mode 100644 index 00000000..81bcb698 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/Math/Vector6.cs @@ -0,0 +1,56 @@ +using System.Runtime.CompilerServices; + +namespace Ryujinx.Audio.Renderer.Utils.Math +{ + record struct Vector6 + { + public float X; + public float Y; + public float Z; + public float W; + public float V; + public float U; + + public Vector6(float value) : this(value, value, value, value, value, value) + { + } + + public Vector6(float x, float y, float z, float w, float v, float u) + { + X = x; + Y = y; + Z = z; + W = w; + V = v; + U = u; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector6 operator +(Vector6 left, Vector6 right) + { + return new Vector6(left.X + right.X, + left.Y + right.Y, + left.Z + right.Z, + left.W + right.W, + left.V + right.V, + left.U + right.U); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector6 operator *(Vector6 left, Vector6 right) + { + return new Vector6(left.X * right.X, + left.Y * right.Y, + left.Z * right.Z, + left.W * right.W, + left.V * right.V, + left.U * right.U); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector6 operator *(Vector6 left, float right) + { + return left * new Vector6(right); + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs b/src/Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs new file mode 100644 index 00000000..103fb6a0 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs @@ -0,0 +1,171 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Utils +{ + /// + /// Helper for IO operations on and . + /// + public static class SpanIOHelper + { + /// + /// Write the given data to the given backing and move cursor after the written data. + /// + /// The data type. + /// The backing to store the data. + /// The data to write to the backing . + public static void Write(ref Memory backingMemory, ref T data) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(); + } + + MemoryMarshal.Write(backingMemory.Span.Slice(0, size), ref data); + + backingMemory = backingMemory.Slice(size); + } + + /// + /// Write the given data to the given backing and move cursor after the written data. + /// + /// The data type. + /// The backing to store the data. + /// The data to write to the backing . + public static void Write(ref Span backingMemory, ref T data) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(); + } + + MemoryMarshal.Write(backingMemory.Slice(0, size), ref data); + + backingMemory = backingMemory.Slice(size); + } + + /// + /// Get a out of a and move cursor after T size. + /// + /// The data type. + /// The backing to get a from. + /// A from backing . + public static Span GetWriteRef(ref Memory backingMemory) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(); + } + + Span result = MemoryMarshal.Cast(backingMemory.Span.Slice(0, size)); + + backingMemory = backingMemory.Slice(size); + + return result; + } + + /// + /// Get a out of a backingMemory and move cursor after T size. + /// + /// The data type. + /// The backing to get a from. + /// A from backing . + public static Span GetWriteRef(ref Span backingMemory) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(); + } + + Span result = MemoryMarshal.Cast(backingMemory.Slice(0, size)); + + backingMemory = backingMemory.Slice(size); + + return result; + } + + /// + /// Read data from the given backing and move cursor after the read data. + /// + /// The data type. + /// The backing to read data from. + /// Return the read data. + public static T Read(ref ReadOnlyMemory backingMemory) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(); + } + + T result = MemoryMarshal.Read(backingMemory.Span.Slice(0, size)); + + backingMemory = backingMemory.Slice(size); + + return result; + } + + /// + /// Read data from the given backing and move cursor after the read data. + /// + /// The data type. + /// The backing to read data from. + /// Return the read data. + public static T Read(ref ReadOnlySpan backingMemory) where T : unmanaged + { + int size = Unsafe.SizeOf(); + + if (size > backingMemory.Length) + { + throw new ArgumentOutOfRangeException(); + } + + T result = MemoryMarshal.Read(backingMemory.Slice(0, size)); + + backingMemory = backingMemory.Slice(size); + + return result; + } + + /// + /// Extract a at the given index. + /// + /// The data type. + /// The to extract the data from. + /// The id in the provided memory. + /// The max allowed count. (for bound checking of the id in debug mode) + /// a at the given id. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Memory GetMemory(Memory memory, int id, uint count) where T : unmanaged + { + Debug.Assert(id >= 0 && id < count); + + return memory.Slice(id, 1); + } + + /// + /// Extract a ref T at the given index. + /// + /// The data type. + /// The to extract the data from. + /// The id in the provided memory. + /// The max allowed count. (for bound checking of the id in debug mode) + /// a ref T at the given id. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetFromMemory(Memory memory, int id, uint count) where T : unmanaged + { + return ref GetMemory(memory, id, count).Span[0]; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Utils/SpanMemoryManager.cs b/src/Ryujinx.Audio/Renderer/Utils/SpanMemoryManager.cs new file mode 100644 index 00000000..2c48da6a --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/SpanMemoryManager.cs @@ -0,0 +1,43 @@ +using System; +using System.Buffers; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Renderer.Utils +{ + public sealed unsafe class SpanMemoryManager : MemoryManager + where T : unmanaged + { + private readonly T* _pointer; + private readonly int _length; + + public SpanMemoryManager(Span span) + { + fixed (T* ptr = &MemoryMarshal.GetReference(span)) + { + _pointer = ptr; + _length = span.Length; + } + } + + public override Span GetSpan() => new Span(_pointer, _length); + + public override MemoryHandle Pin(int elementIndex = 0) + { + if (elementIndex < 0 || elementIndex >= _length) + { + throw new ArgumentOutOfRangeException(nameof(elementIndex)); + } + + return new MemoryHandle(_pointer + elementIndex); + } + + public override void Unpin() { } + + protected override void Dispose(bool disposing) { } + + public static Memory Cast(Memory memory) where TFrom : unmanaged + { + return new SpanMemoryManager(MemoryMarshal.Cast(memory.Span)).Memory; + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs b/src/Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs new file mode 100644 index 00000000..18396078 --- /dev/null +++ b/src/Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs @@ -0,0 +1,58 @@ +using Ryujinx.Audio.Integration; +using System; + +namespace Ryujinx.Audio.Renderer.Utils +{ + public class SplitterHardwareDevice : IHardwareDevice + { + private IHardwareDevice _baseDevice; + private IHardwareDevice _secondaryDevice; + + public SplitterHardwareDevice(IHardwareDevice baseDevice, IHardwareDevice secondaryDevice) + { + _baseDevice = baseDevice; + _secondaryDevice = secondaryDevice; + } + + public void AppendBuffer(ReadOnlySpan data, uint channelCount) + { + _baseDevice.AppendBuffer(data, channelCount); + _secondaryDevice?.AppendBuffer(data, channelCount); + } + + public void SetVolume(float volume) + { + _baseDevice.SetVolume(volume); + _secondaryDevice.SetVolume(volume); + } + + public float GetVolume() + { + return _baseDevice.GetVolume(); + } + + public uint GetChannelCount() + { + return _baseDevice.GetChannelCount(); + } + + public uint GetSampleRate() + { + return _baseDevice.GetSampleRate(); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _baseDevice.Dispose(); + _secondaryDevice?.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/ResultCode.cs b/src/Ryujinx.Audio/ResultCode.cs new file mode 100644 index 00000000..1d05ac65 --- /dev/null +++ b/src/Ryujinx.Audio/ResultCode.cs @@ -0,0 +1,22 @@ +namespace Ryujinx.Audio +{ + public enum ResultCode + { + ModuleId = 153, + ErrorCodeShift = 9, + + Success = 0, + + DeviceNotFound = (1 << ErrorCodeShift) | ModuleId, + OperationFailed = (2 << ErrorCodeShift) | ModuleId, + UnsupportedSampleRate = (3 << ErrorCodeShift) | ModuleId, + WorkBufferTooSmall = (4 << ErrorCodeShift) | ModuleId, + BufferRingFull = (8 << ErrorCodeShift) | ModuleId, + UnsupportedChannelConfiguration = (10 << ErrorCodeShift) | ModuleId, + InvalidUpdateInfo = (41 << ErrorCodeShift) | ModuleId, + InvalidAddressInfo = (42 << ErrorCodeShift) | ModuleId, + InvalidMixSorting = (43 << ErrorCodeShift) | ModuleId, + UnsupportedOperation = (513 << ErrorCodeShift) | ModuleId, + InvalidExecutionContextOperation = (514 << ErrorCodeShift) | ModuleId, + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Ryujinx.Audio.csproj b/src/Ryujinx.Audio/Ryujinx.Audio.csproj new file mode 100644 index 00000000..4a159eb5 --- /dev/null +++ b/src/Ryujinx.Audio/Ryujinx.Audio.csproj @@ -0,0 +1,14 @@ + + + + net7.0 + true + + + + + + + + + diff --git a/src/Ryujinx.Ava/App.axaml b/src/Ryujinx.Ava/App.axaml new file mode 100644 index 00000000..72bc0dee --- /dev/null +++ b/src/Ryujinx.Ava/App.axaml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/src/Ryujinx.Ava/App.axaml.cs b/src/Ryujinx.Ava/App.axaml.cs new file mode 100644 index 00000000..e36cbfdd --- /dev/null +++ b/src/Ryujinx.Ava/App.axaml.cs @@ -0,0 +1,154 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using Avalonia.Styling; +using Avalonia.Threading; +using FluentAvalonia.Styling; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.UI.Controls; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Ui.Common.Configuration; +using Ryujinx.Ui.Common.Helper; +using System; +using System.Diagnostics; +using System.IO; + +namespace Ryujinx.Ava +{ + public class App : Application + { + public override void Initialize() + { + Name = $"Ryujinx {Program.Version}"; + + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow(); + } + + base.OnFrameworkInitializationCompleted(); + + if (Program.PreviewerDetached) + { + ApplyConfiguredTheme(); + + ConfigurationState.Instance.Ui.BaseStyle.Event += ThemeChanged_Event; + ConfigurationState.Instance.Ui.CustomThemePath.Event += ThemeChanged_Event; + ConfigurationState.Instance.Ui.EnableCustomTheme.Event += CustomThemeChanged_Event; + } + } + + private void CustomThemeChanged_Event(object sender, ReactiveEventArgs e) + { + ApplyConfiguredTheme(); + } + + private void ShowRestartDialog() + { +#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + Dispatcher.UIThread.InvokeAsync(async () => + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + var result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogThemeRestartMessage], + LocaleManager.Instance[LocaleKeys.DialogThemeRestartSubMessage], + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.DialogRestartRequiredMessage]); + + if (result == UserResult.Yes) + { + var path = Process.GetCurrentProcess().MainModule.FileName; + var proc = Process.Start(path, CommandLineState.Arguments); + desktop.Shutdown(); + Environment.Exit(0); + } + } + }); +#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed + } + + private void ThemeChanged_Event(object sender, ReactiveEventArgs e) + { + ApplyConfiguredTheme(); + } + + private void ApplyConfiguredTheme() + { + try + { + string baseStyle = ConfigurationState.Instance.Ui.BaseStyle; + string themePath = ConfigurationState.Instance.Ui.CustomThemePath; + bool enableCustomTheme = ConfigurationState.Instance.Ui.EnableCustomTheme; + + const string BaseStyleUrl = "avares://Ryujinx.Ava/Assets/Styles/Base{0}.xaml"; + + if (string.IsNullOrWhiteSpace(baseStyle)) + { + ConfigurationState.Instance.Ui.BaseStyle.Value = "Dark"; + + baseStyle = ConfigurationState.Instance.Ui.BaseStyle; + } + + var theme = AvaloniaLocator.Current.GetService(); + + theme.RequestedTheme = baseStyle; + + var currentStyles = this.Styles; + + // Remove all styles except the base style. + if (currentStyles.Count > 1) + { + currentStyles.RemoveRange(1, currentStyles.Count - 1); + } + + IStyle newStyles = null; + + // Load requested style, and fallback to Dark theme if loading failed. + try + { + newStyles = (Styles)AvaloniaXamlLoader.Load(new Uri(string.Format(BaseStyleUrl, baseStyle), UriKind.Absolute)); + } + catch (XamlLoadException) + { + newStyles = (Styles)AvaloniaXamlLoader.Load(new Uri(string.Format(BaseStyleUrl, "Dark"), UriKind.Absolute)); + } + + currentStyles.Add(newStyles); + + if (enableCustomTheme) + { + if (!string.IsNullOrWhiteSpace(themePath)) + { + try + { + var themeContent = File.ReadAllText(themePath); + var customStyle = AvaloniaRuntimeXamlLoader.Parse(themeContent); + + currentStyles.Add(customStyle); + } + catch (Exception ex) + { + Logger.Error?.Print(LogClass.Application, $"Failed to Apply Custom Theme. Error: {ex.Message}"); + } + } + } + } + catch (Exception) + { + Logger.Warning?.Print(LogClass.Application, "Failed to Apply Theme. A restart is needed to apply the selected theme"); + + ShowRestartDialog(); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Ava/AppHost.cs b/src/Ryujinx.Ava/AppHost.cs new file mode 100644 index 00000000..957a1c9d --- /dev/null +++ b/src/Ryujinx.Ava/AppHost.cs @@ -0,0 +1,1136 @@ +using ARMeilleure.Translation; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Input; +using Avalonia.Threading; +using LibHac.Tools.FsSystem; +using Ryujinx.Audio.Backends.Dummy; +using Ryujinx.Audio.Backends.OpenAL; +using Ryujinx.Audio.Backends.SDL2; +using Ryujinx.Audio.Backends.SoundIo; +using Ryujinx.Audio.Integration; +using Ryujinx.Ava.Common; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Input; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Ava.UI.Models; +using Ryujinx.Ava.UI.Renderer; +using Ryujinx.Ava.UI.ViewModels; +using Ryujinx.Ava.UI.Windows; +using Ryujinx.Common; +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Common.SystemInterop; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.GAL.Multithreading; +using Ryujinx.Graphics.Gpu; +using Ryujinx.Graphics.OpenGL; +using Ryujinx.Graphics.Vulkan; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.HOS.SystemState; +using Ryujinx.Input; +using Ryujinx.Input.HLE; +using Ryujinx.Ui.Common; +using Ryujinx.Ui.Common.Configuration; +using Ryujinx.Ui.Common.Helper; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SPB.Graphics.Vulkan; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop; +using Image = SixLabors.ImageSharp.Image; +using InputManager = Ryujinx.Input.HLE.InputManager; +using Key = Ryujinx.Input.Key; +using MouseButton = Ryujinx.Input.MouseButton; +using Size = Avalonia.Size; +using Switch = Ryujinx.HLE.Switch; + +namespace Ryujinx.Ava +{ + internal class AppHost + { + private const int CursorHideIdleTime = 5; // Hide Cursor seconds. + private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping. + private const int TargetFps = 60; + private const float VolumeDelta = 0.05f; + + private static readonly Cursor InvisibleCursor = new(StandardCursorType.None); + private readonly IntPtr InvisibleCursorWin; + private readonly IntPtr DefaultCursorWin; + + private readonly long _ticksPerFrame; + private readonly Stopwatch _chrono; + private long _ticks; + + private readonly AccountManager _accountManager; + private readonly UserChannelPersistence _userChannelPersistence; + private readonly InputManager _inputManager; + + private readonly MainWindowViewModel _viewModel; + private readonly IKeyboard _keyboardInterface; + private readonly TopLevel _topLevel; + public RendererHost _rendererHost; + + private readonly GraphicsDebugLevel _glLogLevel; + private float _newVolume; + private KeyboardHotkeyState _prevHotkeyState; + + private long _lastCursorMoveTime; + private bool _isCursorInRenderer; + + private bool _isStopped; + private bool _isActive; + private bool _renderingStarted; + + private IRenderer _renderer; + private readonly Thread _renderingThread; + private readonly CancellationTokenSource _gpuCancellationTokenSource; + private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution; + + private bool _dialogShown; + private readonly bool _isFirmwareTitle; + + private readonly object _lockObject = new(); + + public event EventHandler AppExit; + public event EventHandler StatusUpdatedEvent; + + public VirtualFileSystem VirtualFileSystem { get; } + public ContentManager ContentManager { get; } + public NpadManager NpadManager { get; } + public TouchScreenManager TouchScreenManager { get; } + public Switch Device { get; set; } + + public int Width { get; private set; } + public int Height { get; private set; } + public string ApplicationPath { get; private set; } + public bool ScreenshotRequested { get; set; } + + public AppHost( + RendererHost renderer, + InputManager inputManager, + string applicationPath, + VirtualFileSystem virtualFileSystem, + ContentManager contentManager, + AccountManager accountManager, + UserChannelPersistence userChannelPersistence, + MainWindowViewModel viewmodel, + TopLevel topLevel) + { + _viewModel = viewmodel; + _inputManager = inputManager; + _accountManager = accountManager; + _userChannelPersistence = userChannelPersistence; + _renderingThread = new Thread(RenderLoop, 1 * 1024 * 1024) { Name = "GUI.RenderThread" }; + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + _glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel; + _topLevel = topLevel; + + _inputManager.SetMouseDriver(new AvaloniaMouseDriver(_topLevel, renderer)); + + _keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0"); + + NpadManager = _inputManager.CreateNpadManager(); + TouchScreenManager = _inputManager.CreateTouchScreenManager(); + ApplicationPath = applicationPath; + VirtualFileSystem = virtualFileSystem; + ContentManager = contentManager; + + _rendererHost = renderer; + + _chrono = new Stopwatch(); + _ticksPerFrame = Stopwatch.Frequency / TargetFps; + + if (ApplicationPath.StartsWith("@SystemContent")) + { + ApplicationPath = _viewModel.VirtualFileSystem.SwitchPathToSystemPath(ApplicationPath); + + _isFirmwareTitle = true; + } + + ConfigurationState.Instance.HideCursorOnIdle.Event += HideCursorState_Changed; + + _topLevel.PointerMoved += TopLevel_PointerMoved; + + if (OperatingSystem.IsWindows()) + { + InvisibleCursorWin = CreateEmptyCursor(); + DefaultCursorWin = CreateArrowCursor(); + } + + ConfigurationState.Instance.System.IgnoreMissingServices.Event += UpdateIgnoreMissingServicesState; + ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState; + ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState; + ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState; + ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState; + ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState; + ConfigurationState.Instance.Graphics.AntiAliasing.Event += UpdateAntiAliasing; + ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter; + ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel; + + ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState; + + _gpuCancellationTokenSource = new CancellationTokenSource(); + } + + private void TopLevel_PointerMoved(object sender, PointerEventArgs e) + { + if (sender is MainWindow window) + { + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + + if (_rendererHost.EmbeddedWindow.TransformedBounds != null) + { + var point = e.GetCurrentPoint(window).Position; + var bounds = _rendererHost.EmbeddedWindow.TransformedBounds.Value.Clip; + + _isCursorInRenderer = point.X >= bounds.X && + point.X <= bounds.Width + bounds.X && + point.Y >= bounds.Y && + point.Y <= bounds.Height + bounds.Y; + } + } + } + private void UpdateScalingFilterLevel(object sender, ReactiveEventArgs e) + { + _renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); + _renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); + } + + private void UpdateScalingFilter(object sender, ReactiveEventArgs e) + { + _renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); + _renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); + } + + private void ShowCursor() + { + Dispatcher.UIThread.Post(() => + { + _viewModel.Cursor = Cursor.Default; + + if (OperatingSystem.IsWindows()) + { + SetCursor(DefaultCursorWin); + } + }); + } + + private void HideCursor() + { + Dispatcher.UIThread.Post(() => + { + _viewModel.Cursor = InvisibleCursor; + + if (OperatingSystem.IsWindows()) + { + SetCursor(InvisibleCursorWin); + } + }); + } + + private void SetRendererWindowSize(Size size) + { + if (_renderer != null) + { + double scale = _topLevel.PlatformImpl.RenderScaling; + + _renderer.Window?.SetSize((int)(size.Width * scale), (int)(size.Height * scale)); + } + } + + private void Renderer_ScreenCaptured(object sender, ScreenCaptureImageInfo e) + { + if (e.Data.Length > 0 && e.Height > 0 && e.Width > 0) + { + Task.Run(() => + { + lock (_lockObject) + { + DateTime currentTime = DateTime.Now; + string filename = $"ryujinx_capture_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png"; + + string directory = AppDataManager.Mode switch + { + AppDataManager.LaunchMode.Portable => Path.Combine(AppDataManager.BaseDirPath, "screenshots"), + _ => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx") + }; + + string path = Path.Combine(directory, filename); + + try + { + Directory.CreateDirectory(directory); + } + catch (Exception ex) + { + Logger.Error?.Print(LogClass.Application, $"Failed to create directory at path {directory}. Error : {ex.GetType().Name}", "Screenshot"); + + return; + } + + Image image = e.IsBgra ? Image.LoadPixelData(e.Data, e.Width, e.Height) + : Image.LoadPixelData(e.Data, e.Width, e.Height); + + if (e.FlipX) + { + image.Mutate(x => x.Flip(FlipMode.Horizontal)); + } + + if (e.FlipY) + { + image.Mutate(x => x.Flip(FlipMode.Vertical)); + } + + image.SaveAsPng(path, new PngEncoder() + { + ColorType = PngColorType.Rgb + }); + + image.Dispose(); + + Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot"); + } + }); + } + else + { + Logger.Error?.Print(LogClass.Application, $"Screenshot is empty. Size : {e.Data.Length} bytes. Resolution : {e.Width}x{e.Height}", "Screenshot"); + } + } + + public void Start() + { + if (OperatingSystem.IsWindows()) + { + _windowsMultimediaTimerResolution = new WindowsMultimediaTimerResolution(1); + } + + DisplaySleep.Prevent(); + + NpadManager.Initialize(Device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); + TouchScreenManager.Initialize(Device); + + _viewModel.IsGameRunning = true; + + var activeProcess = Device.Processes.ActiveApplication; + + string titleNameSection = string.IsNullOrWhiteSpace(activeProcess.Name) ? string.Empty : $" {activeProcess.Name}"; + string titleVersionSection = string.IsNullOrWhiteSpace(activeProcess.DisplayVersion) ? string.Empty : $" v{activeProcess.DisplayVersion}"; + string titleIdSection = $" ({activeProcess.ProgramIdText.ToUpper()})"; + string titleArchSection = activeProcess.Is64Bit ? " (64-bit)" : " (32-bit)"; + + Dispatcher.UIThread.InvokeAsync(() => + { + _viewModel.Title = $"Ryujinx {Program.Version} -{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}"; + }); + + _viewModel.SetUIProgressHandlers(Device); + + _rendererHost.SizeChanged += Window_SizeChanged; + + _isActive = true; + + _renderingThread.Start(); + + _viewModel.Volume = ConfigurationState.Instance.System.AudioVolume.Value; + + MainLoop(); + + Exit(); + } + + private void UpdateIgnoreMissingServicesState(object sender, ReactiveEventArgs args) + { + if (Device != null) + { + Device.Configuration.IgnoreMissingServices = args.NewValue; + } + } + + private void UpdateAspectRatioState(object sender, ReactiveEventArgs args) + { + if (Device != null) + { + Device.Configuration.AspectRatio = args.NewValue; + } + } + + private void UpdateAntiAliasing(object sender, ReactiveEventArgs e) + { + _renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)e.NewValue); + } + + private void UpdateDockedModeState(object sender, ReactiveEventArgs e) + { + Device?.System.ChangeDockedModeState(e.NewValue); + } + + private void UpdateAudioVolumeState(object sender, ReactiveEventArgs e) + { + Device?.SetVolume(e.NewValue); + + Dispatcher.UIThread.Post(() => + { + _viewModel.Volume = e.NewValue; + }); + } + + private void UpdateLanInterfaceIdState(object sender, ReactiveEventArgs e) + { + Device.Configuration.MultiplayerLanInterfaceId = e.NewValue; + } + + public void Stop() + { + _isActive = false; + } + + private void Exit() + { + (_keyboardInterface as AvaloniaKeyboard)?.Clear(); + + if (_isStopped) + { + return; + } + + _isStopped = true; + _isActive = false; + } + + public void DisposeContext() + { + Dispose(); + + _isActive = false; + + if (_renderingThread.IsAlive) + { + _renderingThread.Join(); + } + + DisplaySleep.Restore(); + + NpadManager.Dispose(); + TouchScreenManager.Dispose(); + Device.Dispose(); + + DisposeGpu(); + + AppExit?.Invoke(this, EventArgs.Empty); + } + + private void Dispose() + { + if (Device.Processes != null) + { + _viewModel.UpdateGameMetadata(Device.Processes.ActiveApplication.ProgramIdText); + } + + ConfigurationState.Instance.System.IgnoreMissingServices.Event -= UpdateIgnoreMissingServicesState; + ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState; + ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState; + ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState; + ConfigurationState.Instance.Graphics.ScalingFilter.Event -= UpdateScalingFilter; + ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event -= UpdateScalingFilterLevel; + ConfigurationState.Instance.Graphics.AntiAliasing.Event -= UpdateAntiAliasing; + + _topLevel.PointerMoved -= TopLevel_PointerMoved; + + _gpuCancellationTokenSource.Cancel(); + _gpuCancellationTokenSource.Dispose(); + + _chrono.Stop(); + } + + public void DisposeGpu() + { + if (OperatingSystem.IsWindows()) + { + _windowsMultimediaTimerResolution?.Dispose(); + _windowsMultimediaTimerResolution = null; + } + + (_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(); + + Device.DisposeGpu(); + + (_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(null); + } + + private void HideCursorState_Changed(object sender, ReactiveEventArgs state) + { + if (state.NewValue) + { + _lastCursorMoveTime = Stopwatch.GetTimestamp(); + } + } + + public async Task LoadGuestApplication() + { + InitializeSwitchInstance(); + MainWindow.UpdateGraphicsConfig(); + + SystemVersion firmwareVersion = ContentManager.GetCurrentFirmwareVersion(); + + if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + if (!SetupValidator.CanStartApplication(ContentManager, ApplicationPath, out UserError userError)) + { + { + if (SetupValidator.CanFixStartApplication(ContentManager, ApplicationPath, userError, out firmwareVersion)) + { + if (userError == UserError.NoFirmware) + { + UserResult result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogFirmwareNoFirmwareInstalledMessage], + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallEmbeddedMessage, firmwareVersion.VersionString), + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + ""); + + if (result != UserResult.Yes) + { + await UserErrorDialog.ShowUserErrorDialog(userError, (desktop.MainWindow as MainWindow)); + Device.Dispose(); + + return false; + } + } + + if (!SetupValidator.TryFixStartApplication(ContentManager, ApplicationPath, userError, out _)) + { + await UserErrorDialog.ShowUserErrorDialog(userError, (desktop.MainWindow as MainWindow)); + Device.Dispose(); + + return false; + } + + // Tell the user that we installed a firmware for them. + if (userError == UserError.NoFirmware) + { + firmwareVersion = ContentManager.GetCurrentFirmwareVersion(); + + _viewModel.RefreshFirmwareStatus(); + + await ContentDialogHelper.CreateInfoDialog( + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstalledMessage, firmwareVersion.VersionString), + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallEmbeddedSuccessMessage, firmwareVersion.VersionString), + LocaleManager.Instance[LocaleKeys.InputDialogOk], + "", + LocaleManager.Instance[LocaleKeys.RyujinxInfo]); + } + } + else + { + await UserErrorDialog.ShowUserErrorDialog(userError, (desktop.MainWindow as MainWindow)); + Device.Dispose(); + + return false; + } + } + } + } + + Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}"); + + if (_isFirmwareTitle) + { + Logger.Info?.Print(LogClass.Application, "Loading as Firmware Title (NCA)."); + + if (!Device.LoadNca(ApplicationPath)) + { + Device.Dispose(); + + return false; + } + } + else if (Directory.Exists(ApplicationPath)) + { + string[] romFsFiles = Directory.GetFiles(ApplicationPath, "*.istorage"); + + if (romFsFiles.Length == 0) + { + romFsFiles = Directory.GetFiles(ApplicationPath, "*.romfs"); + } + + if (romFsFiles.Length > 0) + { + Logger.Info?.Print(LogClass.Application, "Loading as cart with RomFS."); + + if (!Device.LoadCart(ApplicationPath, romFsFiles[0])) + { + Device.Dispose(); + + return false; + } + } + else + { + Logger.Info?.Print(LogClass.Application, "Loading as cart WITHOUT RomFS."); + + if (!Device.LoadCart(ApplicationPath)) + { + Device.Dispose(); + + return false; + } + } + } + else if (File.Exists(ApplicationPath)) + { + switch (Path.GetExtension(ApplicationPath).ToLowerInvariant()) + { + case ".xci": + { + Logger.Info?.Print(LogClass.Application, "Loading as XCI."); + + if (!Device.LoadXci(ApplicationPath)) + { + Device.Dispose(); + + return false; + } + + break; + } + case ".nca": + { + Logger.Info?.Print(LogClass.Application, "Loading as NCA."); + + if (!Device.LoadNca(ApplicationPath)) + { + Device.Dispose(); + + return false; + } + + break; + } + case ".nsp": + case ".pfs0": + { + Logger.Info?.Print(LogClass.Application, "Loading as NSP."); + + if (!Device.LoadNsp(ApplicationPath)) + { + Device.Dispose(); + + return false; + } + + break; + } + default: + { + Logger.Info?.Print(LogClass.Application, "Loading as homebrew."); + + try + { + if (!Device.LoadProgram(ApplicationPath)) + { + Device.Dispose(); + + return false; + } + } + catch (ArgumentOutOfRangeException) + { + Logger.Error?.Print(LogClass.Application, "The specified file is not supported by Ryujinx."); + + Device.Dispose(); + + return false; + } + + break; + } + } + } + else + { + Logger.Warning?.Print(LogClass.Application, "Please specify a valid XCI/NCA/NSP/PFS0/NRO file."); + + Device.Dispose(); + + return false; + } + + DiscordIntegrationModule.SwitchToPlayingState(Device.Processes.ActiveApplication.ProgramIdText, Device.Processes.ActiveApplication.Name); + + _viewModel.ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata => + { + appMetadata.LastPlayed = DateTime.UtcNow.ToString(); + }); + + return true; + } + + internal void Resume() + { + Device?.System.TogglePauseEmulation(false); + + _viewModel.IsPaused = false; + } + + internal void Pause() + { + Device?.System.TogglePauseEmulation(true); + + _viewModel.IsPaused = true; + } + + private void InitializeSwitchInstance() + { + // Initialize KeySet. + VirtualFileSystem.ReloadKeySet(); + + // Initialize Renderer. + IRenderer renderer; + + if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan) + { + renderer = new VulkanRenderer( + (_rendererHost.EmbeddedWindow as EmbeddedWindowVulkan).CreateSurface, + VulkanHelper.GetRequiredInstanceExtensions, + ConfigurationState.Instance.Graphics.PreferredGpu.Value); + } + else + { + renderer = new OpenGLRenderer(); + } + + BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading; + + var isGALthreaded = threadingMode == BackendThreading.On || (threadingMode == BackendThreading.Auto && renderer.PreferThreading); + if (isGALthreaded) + { + renderer = new ThreadedRenderer(renderer); + } + + Logger.Info?.PrintMsg(LogClass.Gpu, $"Backend Threading ({threadingMode}): {isGALthreaded}"); + + // Initialize Configuration. + var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value ? HLE.MemoryConfiguration.MemoryConfiguration6GiB : HLE.MemoryConfiguration.MemoryConfiguration4GiB; + + HLE.HLEConfiguration configuration = new(VirtualFileSystem, + _viewModel.LibHacHorizonManager, + ContentManager, + _accountManager, + _userChannelPersistence, + renderer, + InitializeAudio(), + memoryConfiguration, + _viewModel.UiHandler, + (SystemLanguage)ConfigurationState.Instance.System.Language.Value, + (RegionCode)ConfigurationState.Instance.System.Region.Value, + ConfigurationState.Instance.Graphics.EnableVsync, + ConfigurationState.Instance.System.EnableDockedMode, + ConfigurationState.Instance.System.EnablePtc, + ConfigurationState.Instance.System.EnableInternetAccess, + ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None, + ConfigurationState.Instance.System.FsGlobalAccessLogMode, + ConfigurationState.Instance.System.SystemTimeOffset, + ConfigurationState.Instance.System.TimeZone, + ConfigurationState.Instance.System.MemoryManagerMode, + ConfigurationState.Instance.System.IgnoreMissingServices, + ConfigurationState.Instance.Graphics.AspectRatio, + ConfigurationState.Instance.System.AudioVolume, + ConfigurationState.Instance.System.UseHypervisor, + ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value); + + Device = new Switch(configuration); + } + + private static IHardwareDeviceDriver InitializeAudio() + { + var availableBackends = new List() + { + AudioBackend.SDL2, + AudioBackend.SoundIo, + AudioBackend.OpenAl, + AudioBackend.Dummy + }; + + AudioBackend preferredBackend = ConfigurationState.Instance.System.AudioBackend.Value; + + for (int i = 0; i < availableBackends.Count; i++) + { + if (availableBackends[i] == preferredBackend) + { + availableBackends.RemoveAt(i); + availableBackends.Insert(0, preferredBackend); + break; + } + } + + static IHardwareDeviceDriver InitializeAudioBackend(AudioBackend backend, AudioBackend nextBackend) where T : IHardwareDeviceDriver, new() + { + if (T.IsSupported) + { + return new T(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, $"{backend} is not supported, falling back to {nextBackend}."); + + return null; + } + } + + IHardwareDeviceDriver deviceDriver = null; + + for (int i = 0; i < availableBackends.Count; i++) + { + AudioBackend currentBackend = availableBackends[i]; + AudioBackend nextBackend = i + 1 < availableBackends.Count ? availableBackends[i + 1] : AudioBackend.Dummy; + + deviceDriver = currentBackend switch + { + AudioBackend.SDL2 => InitializeAudioBackend(AudioBackend.SDL2, nextBackend), + AudioBackend.SoundIo => InitializeAudioBackend(AudioBackend.SoundIo, nextBackend), + AudioBackend.OpenAl => InitializeAudioBackend(AudioBackend.OpenAl, nextBackend), + _ => new DummyHardwareDeviceDriver() + }; + + if (deviceDriver != null) + { + ConfigurationState.Instance.System.AudioBackend.Value = currentBackend; + break; + } + } + + MainWindowViewModel.SaveConfig(); + + return deviceDriver; + } + + private void Window_SizeChanged(object sender, Size e) + { + Width = (int)e.Width; + Height = (int)e.Height; + + SetRendererWindowSize(e); + } + + private void MainLoop() + { + while (_isActive) + { + UpdateFrame(); + + // Polling becomes expensive if it's not slept. + Thread.Sleep(1); + } + } + + private void RenderLoop() + { + Dispatcher.UIThread.InvokeAsync(() => + { + if (_viewModel.StartGamesInFullscreen) + { + _viewModel.WindowState = WindowState.FullScreen; + } + + if (_viewModel.WindowState == WindowState.FullScreen) + { + _viewModel.ShowMenuAndStatusBar = false; + } + }); + + _renderer = Device.Gpu.Renderer is ThreadedRenderer tr ? tr.BaseRenderer : Device.Gpu.Renderer; + + _renderer.ScreenCaptured += Renderer_ScreenCaptured; + + (_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.InitializeBackgroundContext(_renderer); + + Device.Gpu.Renderer.Initialize(_glLogLevel); + + _renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)ConfigurationState.Instance.Graphics.AntiAliasing.Value); + _renderer?.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value); + _renderer?.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value); + + Width = (int)_rendererHost.Bounds.Width; + Height = (int)_rendererHost.Bounds.Height; + + _renderer.Window.SetSize((int)(Width * _topLevel.PlatformImpl.RenderScaling), (int)(Height * _topLevel.PlatformImpl.RenderScaling)); + + _chrono.Start(); + + Device.Gpu.Renderer.RunLoop(() => + { + Device.Gpu.SetGpuThread(); + Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token); + Translator.IsReadyForTranslation.Set(); + + _renderer.Window.ChangeVSyncMode(Device.EnableDeviceVsync); + + while (_isActive) + { + _ticks += _chrono.ElapsedTicks; + + _chrono.Restart(); + + if (Device.WaitFifo()) + { + Device.Statistics.RecordFifoStart(); + Device.ProcessFrame(); + Device.Statistics.RecordFifoEnd(); + } + + while (Device.ConsumeFrameAvailable()) + { + if (!_renderingStarted) + { + _renderingStarted = true; + _viewModel.SwitchToRenderer(false); + } + + Device.PresentFrame(() => (_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.SwapBuffers()); + } + + if (_ticks >= _ticksPerFrame) + { + UpdateStatus(); + } + } + }); + + (_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(null); + } + + public void UpdateStatus() + { + // Run a status update only when a frame is to be drawn. This prevents from updating the ui and wasting a render when no frame is queued. + string dockedMode = ConfigurationState.Instance.System.EnableDockedMode ? LocaleManager.Instance[LocaleKeys.Docked] : LocaleManager.Instance[LocaleKeys.Handheld]; + + if (GraphicsConfig.ResScale != 1) + { + dockedMode += $" ({GraphicsConfig.ResScale}x)"; + } + + StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs( + Device.EnableDeviceVsync, + LocaleManager.Instance[LocaleKeys.VolumeShort] + $": {(int)(Device.GetVolume() * 100)}%", + ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan ? "Vulkan" : "OpenGL", + dockedMode, + ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(), + LocaleManager.Instance[LocaleKeys.Game] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)", + $"FIFO: {Device.Statistics.GetFifoPercent():00.00} %", + $"GPU: {_renderer.GetHardwareInfo().GpuVendor}")); + } + + public async Task ShowExitPrompt() + { + bool shouldExit = !ConfigurationState.Instance.ShowConfirmExit; + if (!shouldExit) + { + if (_dialogShown) + { + return; + } + + _dialogShown = true; + + shouldExit = await ContentDialogHelper.CreateStopEmulationDialog(); + + _dialogShown = false; + } + + if (shouldExit) + { + Stop(); + } + } + + private bool UpdateFrame() + { + if (!_isActive) + { + return false; + } + + NpadManager.Update(ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); + + if (_viewModel.IsActive) + { + if (ConfigurationState.Instance.Hid.EnableMouse) + { + if (_isCursorInRenderer) + { + HideCursor(); + } + else + { + ShowCursor(); + } + } + else + { + if (ConfigurationState.Instance.HideCursorOnIdle) + { + if (Stopwatch.GetTimestamp() - _lastCursorMoveTime >= CursorHideIdleTime * Stopwatch.Frequency) + { + HideCursor(); + } + else + { + ShowCursor(); + } + } + } + + Dispatcher.UIThread.Post(() => + { + if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _viewModel.WindowState != WindowState.FullScreen) + { + Device.Processes.ActiveApplication.DiskCacheLoadState?.Cancel(); + } + }); + + KeyboardHotkeyState currentHotkeyState = GetHotkeyState(); + + if (currentHotkeyState != _prevHotkeyState) + { + switch (currentHotkeyState) + { + case KeyboardHotkeyState.ToggleVSync: + Device.EnableDeviceVsync = !Device.EnableDeviceVsync; + + break; + case KeyboardHotkeyState.Screenshot: + ScreenshotRequested = true; + break; + case KeyboardHotkeyState.ShowUi: + _viewModel.ShowMenuAndStatusBar = true; + break; + case KeyboardHotkeyState.Pause: + if (_viewModel.IsPaused) + { + Resume(); + } + else + { + Pause(); + } + break; + case KeyboardHotkeyState.ToggleMute: + if (Device.IsAudioMuted()) + { + Device.SetVolume(ConfigurationState.Instance.System.AudioVolume); + } + else + { + Device.SetVolume(0); + } + + _viewModel.Volume = Device.GetVolume(); + break; + case KeyboardHotkeyState.ResScaleUp: + GraphicsConfig.ResScale = GraphicsConfig.ResScale % MaxResolutionScale + 1; + break; + case KeyboardHotkeyState.ResScaleDown: + GraphicsConfig.ResScale = + (MaxResolutionScale + GraphicsConfig.ResScale - 2) % MaxResolutionScale + 1; + break; + case KeyboardHotkeyState.VolumeUp: + _newVolume = MathF.Round((Device.GetVolume() + VolumeDelta), 2); + Device.SetVolume(_newVolume); + + _viewModel.Volume = Device.GetVolume(); + break; + case KeyboardHotkeyState.VolumeDown: + _newVolume = MathF.Round((Device.GetVolume() - VolumeDelta), 2); + Device.SetVolume(_newVolume); + + _viewModel.Volume = Device.GetVolume(); + break; + case KeyboardHotkeyState.None: + (_keyboardInterface as AvaloniaKeyboard).Clear(); + break; + } + } + + _prevHotkeyState = currentHotkeyState; + + if (ScreenshotRequested) + { + ScreenshotRequested = false; + _renderer.Screenshot(); + } + } + + // Touchscreen. + bool hasTouch = false; + + if (_viewModel.IsActive && !ConfigurationState.Instance.Hid.EnableMouse) + { + hasTouch = TouchScreenManager.Update(true, (_inputManager.MouseDriver as AvaloniaMouseDriver).IsButtonPressed(MouseButton.Button1), ConfigurationState.Instance.Graphics.AspectRatio.Value.ToFloat()); + } + + if (!hasTouch) + { + Device.Hid.Touchscreen.Update(); + } + + Device.Hid.DebugPad.Update(); + + return true; + } + + private KeyboardHotkeyState GetHotkeyState() + { + KeyboardHotkeyState state = KeyboardHotkeyState.None; + + if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleVsync)) + { + state = KeyboardHotkeyState.ToggleVSync; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot)) + { + state = KeyboardHotkeyState.Screenshot; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi)) + { + state = KeyboardHotkeyState.ShowUi; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause)) + { + state = KeyboardHotkeyState.Pause; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ToggleMute)) + { + state = KeyboardHotkeyState.ToggleMute; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ResScaleUp)) + { + state = KeyboardHotkeyState.ResScaleUp; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ResScaleDown)) + { + state = KeyboardHotkeyState.ResScaleDown; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.VolumeUp)) + { + state = KeyboardHotkeyState.VolumeUp; + } + else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.VolumeDown)) + { + state = KeyboardHotkeyState.VolumeDown; + } + + return state; + } + } +} diff --git a/src/Ryujinx.Ava/Assets/Fonts/SegoeFluentIcons.ttf b/src/Ryujinx.Ava/Assets/Fonts/SegoeFluentIcons.ttf new file mode 100644 index 00000000..8f05a4bb Binary files /dev/null and b/src/Ryujinx.Ava/Assets/Fonts/SegoeFluentIcons.ttf differ diff --git a/src/Ryujinx.Ava/Assets/Locales/de_DE.json b/src/Ryujinx.Ava/Assets/Locales/de_DE.json new file mode 100644 index 00000000..8bf00d7e --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/de_DE.json @@ -0,0 +1,614 @@ +{ + "Language": "Deutsch", + "MenuBarFileOpenApplet": "Applet öffnen", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Öffnet das Mii Editor Applet im Standalone Modus", + "SettingsTabInputDirectMouseAccess": "Direkter Mauszugriff", + "SettingsTabSystemMemoryManagerMode": "Speichermanagermodus:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Host (schnell)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host ungeprüft (am schnellsten, unsicher)", + "MenuBarFile": "_Datei", + "MenuBarFileOpenFromFile": "_Datei öffnen", + "MenuBarFileOpenUnpacked": "_Entpacktes Spiel öffnen", + "MenuBarFileOpenEmuFolder": "Ryujinx-Ordner öffnen", + "MenuBarFileOpenLogsFolder": "Logs-Ordner öffnen", + "MenuBarFileExit": "_Beenden", + "MenuBarOptions": "Optionen", + "MenuBarOptionsToggleFullscreen": "Vollbild", + "MenuBarOptionsStartGamesInFullscreen": "Spiele im Vollbildmodus starten", + "MenuBarOptionsStopEmulation": "Emulation beenden", + "MenuBarOptionsSettings": "_Einstellungen", + "MenuBarOptionsManageUserProfiles": "_Profilverwaltung", + "MenuBarActions": "_Aktionen", + "MenuBarOptionsSimulateWakeUpMessage": "Aufwachnachricht", + "MenuBarActionsScanAmiibo": "Scanne ein Amiibo", + "MenuBarTools": "_Werkzeuge", + "MenuBarToolsInstallFirmware": "Firmware installieren", + "MenuBarFileToolsInstallFirmwareFromFile": "Installiere Firmware von einer XCI oder einer ZIP Datei", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Installiere Firmware aus einem Verzeichnis", + "MenuBarHelp": "Hilfe", + "MenuBarHelpCheckForUpdates": "Nach Updates suchen", + "MenuBarHelpAbout": "Über Ryujinx", + "MenuSearch": "Suchen...", + "GameListHeaderFavorite": "Favorit", + "GameListHeaderIcon": "Icon", + "GameListHeaderApplication": "Name", + "GameListHeaderDeveloper": "Entwickler", + "GameListHeaderVersion": "Version", + "GameListHeaderTimePlayed": "Spielzeit", + "GameListHeaderLastPlayed": "Zuletzt gespielt", + "GameListHeaderFileExtension": "Dateiformat", + "GameListHeaderFileSize": "Dateigröße", + "GameListHeaderPath": "Pfad", + "GameListContextMenuOpenUserSaveDirectory": "Spielstand-Verzeichnis öffnen", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Öffnet das Verzeichnis, welches den Benutzer-Spielstand beinhaltet", + "GameListContextMenuOpenDeviceSaveDirectory": "Benutzer-Geräte-Verzeichnis öffnen", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Öffnet das Verzeichnis, welches den Geräte-Spielstände beinhaltet", + "GameListContextMenuOpenBcatSaveDirectory": "Benutzer-BCAT-Vezeichnis öffnen", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Öffnet das Verzeichnis, welches den BCAT Cache des Spiels beinhaltet", + "GameListContextMenuManageTitleUpdates": "Verwalten von Spiel Updates", + "GameListContextMenuManageTitleUpdatesToolTip": "Öffnet den Spiel-Update-Manager", + "GameListContextMenuManageDlc": "Verwalten von DLC", + "GameListContextMenuManageDlcToolTip": "Öffnet den DLC-Manager", + "GameListContextMenuOpenModsDirectory": "Mod-Verzeichnis öffnen", + "GameListContextMenuOpenModsDirectoryToolTip": "Öffnet das Verzeichnis, welches Mods für die Spiele beinhaltet", + "GameListContextMenuCacheManagement": "Cache Verwaltung", + "GameListContextMenuCacheManagementPurgePptc": "PPTC als ungültig markieren", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Markiert den PPTC als ungültig, sodass dieser beim nächsten Spielstart neu erstellt wird", + "GameListContextMenuCacheManagementPurgeShaderCache": "Shader Cache löschen", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Löscht den Shader Cache der Anwendung", + "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC Verzeichnis öffnen", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Öffnet das Verzeichnis, das den PPTC Cache der Anwendung beinhaltet", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Shader Cache Verzeichnis öffnen", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Öffnet das Verzeichnis, das den Shader Cache der Anwendung beinhaltet", + "GameListContextMenuExtractData": "Daten extrahieren", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extrahiert das ExeFS aus der aktuellen Anwendungskonfiguration (einschließlich Updates)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extrahiert das RomFS aus der aktuellen Anwendungskonfiguration (einschließlich Updates)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Extrahiert das Logo aus der aktuellen Anwendungskonfiguration (einschließlich Updates)", + "StatusBarGamesLoaded": "{0}/{1} Spiele geladen", + "StatusBarSystemVersion": "Systemversion: {0}", + "Settings": "Einstellungen", + "SettingsTabGeneral": "Allgemein", + "SettingsTabGeneralGeneral": "Allgemein", + "SettingsTabGeneralEnableDiscordRichPresence": "Aktiviere Discord Rich Presence", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Beim Start nach Updates suchen", + "SettingsTabGeneralShowConfirmExitDialog": "Zeige den \"Beenden bestätigen\" Dialog", + "SettingsTabGeneralHideCursorOnIdle": "Mauszeiger bei Inaktivität ausblenden", + "SettingsTabGeneralGameDirectories": "Spielverzeichnisse", + "SettingsTabGeneralAdd": "Hinzufügen", + "SettingsTabGeneralRemove": "Entfernen", + "SettingsTabSystem": "System", + "SettingsTabSystemCore": "Kern", + "SettingsTabSystemSystemRegion": "Systemregion:", + "SettingsTabSystemSystemRegionJapan": "Japan", + "SettingsTabSystemSystemRegionUSA": "USA", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Australien", + "SettingsTabSystemSystemRegionChina": "China", + "SettingsTabSystemSystemRegionKorea": "Korea", + "SettingsTabSystemSystemRegionTaiwan": "Taiwan", + "SettingsTabSystemSystemLanguage": "Systemsprache:", + "SettingsTabSystemSystemLanguageJapanese": "Japanisch", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Amerikanisches Englisch", + "SettingsTabSystemSystemLanguageFrench": "Französisch", + "SettingsTabSystemSystemLanguageGerman": "Deutsch", + "SettingsTabSystemSystemLanguageItalian": "Italienisch", + "SettingsTabSystemSystemLanguageSpanish": "Spanisch", + "SettingsTabSystemSystemLanguageChinese": "Chinesisch", + "SettingsTabSystemSystemLanguageKorean": "Koreanisch", + "SettingsTabSystemSystemLanguageDutch": "Niederländisch", + "SettingsTabSystemSystemLanguagePortuguese": "Portugiesisch", + "SettingsTabSystemSystemLanguageRussian": "Russisch", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanesisch", + "SettingsTabSystemSystemLanguageBritishEnglish": "Britisches Englisch", + "SettingsTabSystemSystemLanguageCanadianFrench": "Kanadisches Französisch", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Lateinamerikanisches Spanisch", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Vereinfachtes Chinesisch", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Traditionelles Chinesisch", + "SettingsTabSystemSystemTimeZone": "System Zeitzone:", + "SettingsTabSystemSystemTime": "System Zeit:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC Cache (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "FS Integritätsprüfung", + "SettingsTabSystemAudioBackend": "Audio-Backend:", + "SettingsTabSystemAudioBackendDummy": "Ohne Funktion", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": " (Kann Fehler verursachen)", + "SettingsTabSystemExpandDramSize": "Erweitere DRAM Größe auf 6GiB", + "SettingsTabSystemIgnoreMissingServices": "Ignoriere fehlende Dienste", + "SettingsTabGraphics": "Grafik", + "SettingsTabGraphicsAPI": "Grafik-API", + "SettingsTabGraphicsEnableShaderCache": "Shader Cache", + "SettingsTabGraphicsAnisotropicFiltering": "Anisotrope Filterung:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Auflösungsskalierung:", + "SettingsTabGraphicsResolutionScaleCustom": "Benutzerdefiniert (nicht empfohlen)", + "SettingsTabGraphicsResolutionScaleNative": "Nativ (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Bildseitenverhältnis:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Dehnen, um sich an das Fenster anzupassen", + "SettingsTabGraphicsDeveloperOptions": "Optionen für Entwickler", + "SettingsTabGraphicsShaderDumpPath": "Grafik-Shader Dump Pfad:", + "SettingsTabLogging": "Logs", + "SettingsTabLoggingLogging": "Logs", + "SettingsTabLoggingEnableLoggingToFile": "Aktiviere Erstellung von Log-Datei", + "SettingsTabLoggingEnableStubLogs": "Aktiviere Stub-Logs", + "SettingsTabLoggingEnableInfoLogs": "Aktiviere Info-Logs", + "SettingsTabLoggingEnableWarningLogs": "Aktiviere Warn-Logs", + "SettingsTabLoggingEnableErrorLogs": "Aktiviere Fehler-Logs", + "SettingsTabLoggingEnableTraceLogs": "Aktiviere Trace-Logs", + "SettingsTabLoggingEnableGuestLogs": "Aktiviere Gast-Logs", + "SettingsTabLoggingEnableFsAccessLogs": "Aktiviere Fs Zugriff-Logs", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs Globaler Zugriff-Log-Modus:", + "SettingsTabLoggingDeveloperOptions": "Entwickleroptionen (WARNUNG: Beeinträchtigt die Leistung)", + "SettingsTabLoggingGraphicsBackendLogLevel": "Graphics Backend Log Level:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Keine", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Fehler", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Verlangsamungen", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Alle", + "SettingsTabLoggingEnableDebugLogs": "Aktiviere Debug-Log", + "SettingsTabInput": "Eingabe", + "SettingsTabInputEnableDockedMode": "Docked Modus", + "SettingsTabInputDirectKeyboardAccess": "Direkter Tastaturzugriff", + "SettingsButtonSave": "Speichern", + "SettingsButtonClose": "Schließen", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Abbrechen", + "SettingsButtonApply": "Übernehmen", + "ControllerSettingsPlayer": "Spieler", + "ControllerSettingsPlayer1": "Spieler 1", + "ControllerSettingsPlayer2": "Spieler 2", + "ControllerSettingsPlayer3": "Spieler 3", + "ControllerSettingsPlayer4": "Spieler 4", + "ControllerSettingsPlayer5": "Spieler 5", + "ControllerSettingsPlayer6": "Spieler 6", + "ControllerSettingsPlayer7": "Spieler 7", + "ControllerSettingsPlayer8": "Spieler 8", + "ControllerSettingsHandheld": "Handheld", + "ControllerSettingsInputDevice": "Eingabegerät", + "ControllerSettingsRefresh": "Aktualisieren", + "ControllerSettingsDeviceDisabled": "Deaktiviert", + "ControllerSettingsControllerType": "Controller Typ", + "ControllerSettingsControllerTypeHandheld": "Handheld", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "Joy-Con Paar", + "ControllerSettingsControllerTypeJoyConLeft": "Joy-Con Links", + "ControllerSettingsControllerTypeJoyConRight": "Joy-Con Rechts", + "ControllerSettingsProfile": "Profil", + "ControllerSettingsProfileDefault": "Standard", + "ControllerSettingsLoad": "Laden", + "ControllerSettingsAdd": "Hinzufügen", + "ControllerSettingsRemove": "Entfernen", + "ControllerSettingsButtons": "Aktionstasten", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Steuerkreuz", + "ControllerSettingsDPadUp": "Hoch", + "ControllerSettingsDPadDown": "Runter", + "ControllerSettingsDPadLeft": "Links", + "ControllerSettingsDPadRight": "Rechts", + "ControllerSettingsLStick": "Linker Analogstick", + "ControllerSettingsLStickButton": "L3", + "ControllerSettingsLStickUp": "Hoch", + "ControllerSettingsLStickDown": "Runter", + "ControllerSettingsLStickLeft": "Links", + "ControllerSettingsLStickRight": "Rechts", + "ControllerSettingsLStickStick": "Analogstick", + "ControllerSettingsLStickInvertXAxis": "X-Achse invertieren", + "ControllerSettingsLStickInvertYAxis": "Y-Achse invertieren", + "ControllerSettingsLStickDeadzone": "Deadzone:", + "ControllerSettingsRStick": "Rechter Analogstick", + "ControllerSettingsRStickButton": "R3", + "ControllerSettingsRStickUp": "Hoch", + "ControllerSettingsRStickDown": "Runter", + "ControllerSettingsRStickLeft": "Links", + "ControllerSettingsRStickRight": "Rechts", + "ControllerSettingsRStickStick": "Analogstick", + "ControllerSettingsRStickInvertXAxis": "X-Achse invertieren", + "ControllerSettingsRStickInvertYAxis": "Y-Achse invertieren", + "ControllerSettingsRStickDeadzone": "Deadzone:", + "ControllerSettingsTriggersLeft": "Linker Trigger", + "ControllerSettingsTriggersRight": "Rechter Trigger", + "ControllerSettingsTriggersButtonsLeft": "Linke Schultertaste", + "ControllerSettingsTriggersButtonsRight": "Rechte Schultertaste", + "ControllerSettingsTriggers": "Trigger", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Linke Aktionstasten", + "ControllerSettingsExtraButtonsRight": "Rechte Aktionstasten", + "ControllerSettingsMisc": "Verschiedenes", + "ControllerSettingsTriggerThreshold": "Empfindlichkeit:", + "ControllerSettingsMotion": "Bewegung", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook kompatible Bewegungssteuerung", + "ControllerSettingsMotionControllerSlot": "Kontroller Slot:", + "ControllerSettingsMotionMirrorInput": "Spiegele Eingabe", + "ControllerSettingsMotionRightJoyConSlot": "Rechter Joy-Con Slot:", + "ControllerSettingsMotionServerHost": "Server Host:", + "ControllerSettingsMotionGyroSensitivity": "Gyro Empfindlichkeit:", + "ControllerSettingsMotionGyroDeadzone": "Gyro Deadzone:", + "ControllerSettingsSave": "Speichern", + "ControllerSettingsClose": "Schließen", + "UserProfilesSelectedUserProfile": "Ausgewähltes Profil:", + "UserProfilesSaveProfileName": "Profilname speichern", + "UserProfilesChangeProfileImage": "Profilbild ändern", + "UserProfilesAvailableUserProfiles": "Verfügbare Profile:", + "UserProfilesAddNewProfile": "Neues Profil", + "UserProfilesDelete": "Löschen", + "UserProfilesClose": "Schließen", + "ProfileImageSelectionTitle": "Auswahl des Profilbildes", + "ProfileImageSelectionHeader": "Wähle ein Profilbild aus", + "ProfileImageSelectionNote": "Es kann ein eigenes Profilbild importiert werden oder ein Avatar aus der System-Firmware", + "ProfileImageSelectionImportImage": "Bilddatei importieren", + "ProfileImageSelectionSelectAvatar": "Firmware Avatar auswählen", + "InputDialogTitle": "Eingabe Dialog", + "InputDialogOk": "OK", + "InputDialogCancel": "Abbrechen", + "InputDialogAddNewProfileTitle": "Wähle den Profilnamen", + "InputDialogAddNewProfileHeader": "Bitte gebe einen Profilnamen ein", + "InputDialogAddNewProfileSubtext": "(Maximale Länge: {0})", + "AvatarChoose": "Bestätigen", + "AvatarSetBackgroundColor": "Hintergrundfarbe einstellen", + "AvatarClose": "Schließen", + "ControllerSettingsLoadProfileToolTip": "Lädt ein Profil", + "ControllerSettingsAddProfileToolTip": "Fügt ein Profil hinzu", + "ControllerSettingsRemoveProfileToolTip": "Entfernt ein Profil", + "ControllerSettingsSaveProfileToolTip": "Speichert ein Profil", + "MenuBarFileToolsTakeScreenshot": "Screenshot aufnehmen", + "MenuBarFileToolsHideUi": "Hide UI", + "GameListContextMenuToggleFavorite": "Als Favoriten hinzufügen/entfernen", + "GameListContextMenuToggleFavoriteToolTip": "Aktiviert den Favoriten-Status des Spiels", + "SettingsTabGeneralTheme": "Design", + "SettingsTabGeneralThemeCustomTheme": "Pfad für das benutzerdefinierte Design", + "SettingsTabGeneralThemeBaseStyle": "Farbschema", + "SettingsTabGeneralThemeBaseStyleDark": "Dunkel", + "SettingsTabGeneralThemeBaseStyleLight": "Hell", + "SettingsTabGeneralThemeEnableCustomTheme": "Design für die Emulator-Benutzeroberfläche", + "ButtonBrowse": "Durchsuchen", + "ControllerSettingsConfigureGeneral": "Konfigurieren", + "ControllerSettingsRumble": "Vibration", + "ControllerSettingsRumbleStrongMultiplier": "Starker Vibrations-Multiplikator", + "ControllerSettingsRumbleWeakMultiplier": "Schwacher Vibrations-Multiplikator", + "DialogMessageSaveNotAvailableMessage": "Es existieren keine Speicherdaten für {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Soll Ryujinx Speicherdaten für dieses Spiel erstellen?", + "DialogConfirmationTitle": "Ryujinx - Bestätigung", + "DialogUpdaterTitle": "Ryujinx - Updater", + "DialogErrorTitle": "Ryujinx - Fehler", + "DialogWarningTitle": "Ryujinx - Warnung", + "DialogExitTitle": "Ryujinx - Beenden", + "DialogErrorMessage": "Ein Fehler ist aufgetreten", + "DialogExitMessage": "Ryujinx wirklich schließen?", + "DialogExitSubMessage": "Alle nicht gespeicherten Daten gehen verloren!", + "DialogMessageCreateSaveErrorMessage": "Es ist ein Fehler bei der Erstellung der angegebenen Speicherdaten aufgetreten: {0}", + "DialogMessageFindSaveErrorMessage": "Es ist ein Fehler beim Auffinden der angegebenen Speicherdaten aufgetreten: {0}", + "FolderDialogExtractTitle": "Wähle den Ordner, in welchen die Dateien entpackt werden sollen", + "DialogNcaExtractionMessage": "Extrahiert {0} abschnitt von {1}...", + "DialogNcaExtractionTitle": "Ryujinx - NCA-Abschnitt-Extraktor", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Extraktion fehlgeschlagen. Der Hauptheader der NCA war in der ausgewählten Datei nicht vorhanden.", + "DialogNcaExtractionCheckLogErrorMessage": "Extraktion fehlgeschlagen. Überprüfe die Logs für weitere Informationen.", + "DialogNcaExtractionSuccessMessage": "Extraktion erfolgreich abgeschlossen.", + "DialogUpdaterConvertFailedMessage": "Die Konvertierung der aktuellen Ryujinx Version ist fehlgeschlagen.", + "DialogUpdaterCancelUpdateMessage": "Download wird abgebrochen!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Es wird bereits die aktuellste Version von Ryujinx benutzt", + "DialogUpdaterFailedToGetVersionMessage": "An error has occurred when trying to get release information from GitHub Release. This can be caused if a new release is being compiled by GitHub Actions. Try again in a few minutes.", + "DialogUpdaterConvertFailedGithubMessage": "Failed to convert the received Ryujinx version from Github Release.", + "DialogUpdaterDownloadingMessage": "Update wird Heruntergeladen...", + "DialogUpdaterExtractionMessage": "Update wird entpackt...", + "DialogUpdaterRenamingMessage": "Update wird umbenannt...", + "DialogUpdaterAddingFilesMessage": "Update wird hinzugefügt...", + "DialogUpdaterCompleteMessage": "Update abgeschlossen!", + "DialogUpdaterRestartMessage": "Ryujinx jetzt neu starten?", + "DialogUpdaterArchNotSupportedMessage": "Eine nicht unterstützte Systemarchitektur wird benutzt!", + "DialogUpdaterArchNotSupportedSubMessage": "Nur x64 Systeme werden unterstützt!", + "DialogUpdaterNoInternetMessage": "Es besteht keine Verbindung mit dem Internet!", + "DialogUpdaterNoInternetSubMessage": "Bitte vergewissern, dass eine funktionierende Internetverbindung existiert!", + "DialogUpdaterDirtyBuildMessage": "Inoffizielle Versionen von Ryujinx können nicht aktualisiert werden", + "DialogUpdaterDirtyBuildSubMessage": "Lade Ryujinx bitte von hier herunter, um eine unterstützte Version zu erhalten: https://ryujinx.org/", + "DialogRestartRequiredMessage": "Neustart erforderlich", + "DialogThemeRestartMessage": "Das Design wurde gespeichert. Ein Neustart ist erforderlich, um das Design anzuwenden.", + "DialogThemeRestartSubMessage": "Jetzt neu starten?", + "DialogFirmwareInstallEmbeddedMessage": "Die in diesem Spiel enthaltene Firmware installieren? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Es wurde keine installierte Firmware gefunden, aber Ryujinx konnte die Firmware {0} aus dem bereitgestellten Spiel installieren.\nRyujinx wird nun gestartet.", + "DialogFirmwareNoFirmwareInstalledMessage": "Keine Firmware installiert", + "DialogFirmwareInstalledMessage": "Firmware {0} wurde installiert", + "DialogOpenSettingsWindowLabel": "Fenster-Einstellungen öffnen", + "DialogControllerAppletTitle": "Controller-Applet", + "DialogMessageDialogErrorExceptionMessage": "Fehler bei der Anzeige des Meldungs-Dialogs: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Fehler bei der Anzeige der Software-Tastatur: {0}", + "DialogErrorAppletErrorExceptionMessage": "Fehler beim Anzeigen des ErrorApplet-Dialogs: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nWeitere Informationen zur Behebung dieses Fehlers können in unserem Setup-Guide gefunden werden.", + "DialogUserErrorDialogTitle": "Ryujinx Fehler ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Beim Abrufen von Informationen aus der API ist ein Fehler aufgetreten.", + "DialogAmiiboApiConnectErrorMessage": "Verbindung zum Amiibo API Server kann nicht hergestellt werden. Der Dienst ist möglicherweise nicht verfügbar oder es existiert keine Internetverbindung.", + "DialogProfileInvalidProfileErrorMessage": "Das Profil {0} ist mit dem aktuellen Eingabekonfigurationssystem nicht kompatibel.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Das Standardprofil kann nicht überschrieben werden", + "DialogProfileDeleteProfileTitle": "Profil löschen", + "DialogProfileDeleteProfileMessage": "Diese Aktion kann nicht rückgängig gemacht werden. Wirklich fortfahren?", + "DialogWarning": "Warnung", + "DialogPPTCDeletionMessage": "Du bist dabei den PPTC für das folgende Spiel als ungültig zu markieren:\n\n{0}\n\nWirklich fortfahren?", + "DialogPPTCDeletionErrorMessage": "Fehler bei der Löschung des PPTC Caches bei {0}: {1}", + "DialogShaderDeletionMessage": "Du bist dabei, den Shader Cache zu löschen für :\n\n{0}\n\nWirklich fortfahren?", + "DialogShaderDeletionErrorMessage": "Es ist ein Fehler bei der Löschung des Shader Caches bei {0}: {1} aufgetreten", + "DialogRyujinxErrorMessage": "Ein Fehler ist aufgetreten", + "DialogInvalidTitleIdErrorMessage": "UI Fehler: Das ausgewählte Spiel hat keine gültige Titel-ID", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Es wurde keine gültige System-Firmware gefunden in {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Installiere Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Systemversion {0} wird jetzt installiert.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nDies wird die aktuelle Systemversion {0} ersetzen.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nMöchtest du fortfahren?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Firmware wird installiert...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Systemversion {0} wurde erfolgreich installiert.", + "DialogUserProfileDeletionWarningMessage": "Es können keine anderen Profile geöffnet werden, wenn das ausgewählte Profil gelöscht wird.", + "DialogUserProfileDeletionConfirmMessage": "Möchtest du das ausgewählte Profil löschen?", + "DialogControllerSettingsModifiedConfirmMessage": "Die aktuellen Controller-Einstellungen wurden aktualisiert.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Controller-Einstellungen speichern?", + "DialogLoadNcaErrorMessage": "{0}. Fehlerhafte Datei: {1}", + "DialogDlcNoDlcErrorMessage": "Die angegebene Datei enthält keinen DLC für den ausgewählten Titel!", + "DialogPerformanceCheckLoggingEnabledMessage": "Es wurde die Debug Protokollierung aktiviert", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Um eine optimale Leistung zu erzielen, wird empfohlen, die Debug Protokollierung zu deaktivieren. Debug Protokollierung jetzt deaktivieren?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Es wurde das Shader Dumping aktiviert, das nur von Entwicklern verwendet werden soll.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Für eine optimale Leistung wird empfohlen, das Shader Dumping zu deaktivieren. Shader Dumping jetzt deaktivieren?", + "DialogLoadAppGameAlreadyLoadedMessage": "Es wurde bereits ein Spiel gestartet", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Bitte beende die Emulation oder schließe den Emulator, vor dem Starten eines neuen Spiels", + "DialogUpdateAddUpdateErrorMessage": "Die angegebene Datei enthält keine Updates für den ausgewählten Titel!", + "DialogSettingsBackendThreadingWarningTitle": "Warnung - Render Threading", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx muss muss neu gestartet werden, damit die Änderungen wirksam werden. Abhängig von dem Betriebssystem muss möglicherweise das Multithreading des Treibers manuell deaktiviert werden, wenn Ryujinx verwendet wird.", + "SettingsTabGraphicsFeaturesOptions": "Erweiterungen", + "SettingsTabGraphicsBackendMultithreading": "Grafik-Backend Multithreading:", + "CommonAuto": "Auto", + "CommonOff": "Aus", + "CommonOn": "An", + "InputDialogYes": "Ja", + "InputDialogNo": "Nein", + "DialogProfileInvalidProfileNameErrorMessage": "Der Dateiname enthält ungültige Zeichen. Bitte erneut versuchen.", + "MenuBarOptionsPauseEmulation": "Pause", + "MenuBarOptionsResumeEmulation": "Fortsetzen", + "AboutUrlTooltipMessage": "Klicke hier, um die Ryujinx Website im Standardbrowser zu öffnen.", + "AboutDisclaimerMessage": "Ryujinx ist in keinster Weise weder mit Nintendo™, \nnoch mit deren Partnern verbunden.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) wird in unserer Amiibo \nEmulation benutzt.", + "AboutPatreonUrlTooltipMessage": "Klicke hier, um die Ryujinx Patreon Seite im Standardbrowser zu öffnen.", + "AboutGithubUrlTooltipMessage": "Klicke hier, um die Ryujinx GitHub Seite im Standardbrowser zu öffnen.", + "AboutDiscordUrlTooltipMessage": "Klicke hier, um eine Einladung zum Ryujinx Discord Server im Standardbrowser zu öffnen.", + "AboutTwitterUrlTooltipMessage": "Klicke hier, um die Ryujinx Twitter Seite im Standardbrowser zu öffnen.", + "AboutRyujinxAboutTitle": "Über:", + "AboutRyujinxAboutContent": "Ryujinx ist ein Nintendo Switch™ Emulator.\nBitte unterstütze uns auf Patreon.\nAuf Twitter oder Discord erfährst du alle Neuigkeiten.\nEntwickler, die an einer Mitarbeit interessiert sind, können auf GitHub oder Discord mehr erfahren.", + "AboutRyujinxMaintainersTitle": "Entwickelt von:", + "AboutRyujinxMaintainersContentTooltipMessage": "Klicke hier, um die Liste der Mitwirkenden im Standardbrowser zu öffnen.", + "AboutRyujinxSupprtersTitle": "Unterstützt auf Patreon von:", + "AmiiboSeriesLabel": "Amiibo Serie", + "AmiiboCharacterLabel": "Charakter", + "AmiiboScanButtonLabel": "Einscannen", + "AmiiboOptionsShowAllLabel": "Zeige alle Amiibos", + "AmiiboOptionsUsRandomTagLabel": "Hack: Benutze zufällige Tag-UUID", + "DlcManagerTableHeadingEnabledLabel": "Aktiviert", + "DlcManagerTableHeadingTitleIdLabel": "Title-ID", + "DlcManagerTableHeadingContainerPathLabel": "Container-Pfad", + "DlcManagerTableHeadingFullPathLabel": "Vollständiger-Pfad", + "DlcManagerRemoveAllButton": "Entferne alle", + "DlcManagerEnableAllButton": "Alle aktivieren", + "DlcManagerDisableAllButton": "Alle deaktivieren", + "MenuBarOptionsChangeLanguage": "Sprache ändern", + "CommonSort": "Sortieren", + "CommonShowNames": "Spiel-Namen anzeigen", + "CommonFavorite": "Favoriten", + "OrderAscending": "Aufsteigend", + "OrderDescending": "Absteigend", + "SettingsTabGraphicsFeatures": "Erweiterungen", + "ErrorWindowTitle": "Fehler-Fenster", + "ToggleDiscordTooltip": "Zeige momentanes Spiel auf Discord", + "AddGameDirBoxTooltip": "Gibt das Spielverzeichnis an, das der Liste hinzuzufügt wird", + "AddGameDirTooltip": "Fügt ein neues Spielverzeichnis hinzu", + "RemoveGameDirTooltip": "Entfernt das ausgewähltes Spielverzeichnis", + "CustomThemeCheckTooltip": "Verwende ein eigenes Design für die Emulator-Benutzeroberfläche", + "CustomThemePathTooltip": "Gibt den Pfad zum Design für die Emulator-Benutzeroberfläche an", + "CustomThemeBrowseTooltip": "Ermöglicht die Suche nach einem benutzerdefinierten Design für die Emulator-Benutzeroberfläche", + "DockModeToggleTooltip": "Im gedockten Modus verhält sich das emulierte System wie eine Nintendo Switch im TV Modus. Dies verbessert die grafische Qualität der meisten Spiele. Umgekehrt führt die Deaktivierung dazu, dass sich das emulierte System wie eine Nintendo Switch im Handheld Modus verhält, was die Grafikqualität beeinträchtigt.\n\nKonfiguriere das Eingabegerät für Spieler 1, um im Docked Modus zu spielen; konfiguriere das Controllerprofil via der Handheld Option, wenn geplant wird den Handheld Modus zu nutzen.\n\nIm Zweifelsfall AN lassen.", + "DirectKeyboardTooltip": "Aktiviert/Deaktiviert den \"Direkter Tastaturzugriff (HID) Unterstützung\" (Ermöglicht die Benutzung der Tastaur als Eingabegerät in Spielen)", + "DirectMouseTooltip": "Aktiviert/Deaktiviert den \"Direkten Mauszugriff (HID) Unterstützung\" (Ermöglicht die Benutzung der Maus als Eingabegerät in Spielen)", + "RegionTooltip": "Ändert die Systemregion", + "LanguageTooltip": "Ändert die Systemsprache", + "TimezoneTooltip": "Ändert die Systemzeitzone", + "TimeTooltip": "Ändert die Systemzeit", + "VSyncToggleTooltip": "Vertikale Synchronisierung der emulierten Konsole. Diese Option ist ein Frame-Limiter für die meisten Spiele; die Deaktivierung kann dazu führen, dass Spiele mit höherer Geschwindigkeit laufen, Ladebildschirme länger benötigen oder hängen bleiben.\n\nKann beim Spielen mit einem frei wählbaren Hotkey ein- und ausgeschaltet werden.\n\nIm Zweifelsfall AN lassen.", + "PptcToggleTooltip": "Speichert übersetzte JIT-Funktionen, sodass jene nicht jedes Mal übersetzt werden müssen, wenn das Spiel geladen wird.\n\nVerringert Stottern und die Zeit beim zweiten und den darauffolgenden Startvorgängen eines Spiels erheblich.\n\nIm Zweifelsfall AN lassen.", + "FsIntegrityToggleTooltip": "Prüft beim Startvorgang auf beschädigte Dateien und zeigt bei beschädigten Dateien einen Hash-Fehler (Hash Error) im Log an.\n\nDiese Einstellung hat keinen Einfluss auf die Leistung und hilft bei der Fehlersuche.\n\nIm Zweifelsfall AN lassen.", + "AudioBackendTooltip": "Ändert das Backend, das zum Rendern von Audio verwendet wird.\n\nSDL2 ist das bevorzugte Audio-Backend, OpenAL und SoundIO sind als Alternativen vorhanden. Dummy wird keinen Audio-Output haben.\n\nIm Zweifelsfall SDL2 auswählen.", + "MemoryManagerTooltip": "Ändert wie der Gastspeicher abgebildet wird und wie auf ihn zugegriffen wird. Beinflusst die Leistung der emulierten CPU erheblich.\n\nIm Zweifelsfall Host ungeprüft auswählen.", + "MemoryManagerSoftwareTooltip": "Verwendung einer Software-Seitentabelle für die Adressumsetzung. Höchste Genauigkeit, aber langsamste Leistung.", + "MemoryManagerHostTooltip": "Direkte Zuordnung von Speicher im Host-Adressraum. Viel schnellere JIT-Kompilierung und Ausführung.", + "MemoryManagerUnsafeTooltip": "Direkte Zuordnung des Speichers, aber keine Maskierung der Adresse innerhalb des Gastadressraums vor dem Zugriff. Schneller, aber auf Kosten der Sicherheit. Die Gastanwendung kann von überall in Ryujinx auf den Speicher zugreifen, daher sollte in diesem Modus nur Programme ausgeführt werden denen vertraut wird.", + "DRamTooltip": "Erhöht den Arbeitsspeicher des emulierten Systems von 4 GiB auf 6 GiB.\n\nDies ist nur für Texturenpakete mit höherer Auflösung oder Mods mit 4K-Auflösung nützlich. Diese Option verbessert NICHT die Leistung.\n\nIm Zweifelsfall AUS lassen.", + "IgnoreMissingServicesTooltip": "Durch diese Option werden nicht implementierte Dienste der Switch-Firmware ignoriert. Dies kann dabei helfen, Abstürze beim Starten bestimmter Spiele zu umgehen.\n\nIm Zweifelsfall AUS lassen.", + "GraphicsBackendThreadingTooltip": "Führt Grafik-Backend Befehle auf einem zweiten Thread aus.\n\nDies beschleunigt die Shader-Kompilierung, reduziert Stottern und verbessert die Leistung auf GPU-Treibern ohne eigene Multithreading-Unterstützung. Geringfügig bessere Leistung bei Treibern mit Multithreading.\n\nIm Zweifelsfall auf AUTO stellen.", + "GalThreadingTooltip": "Führt Grafik-Backend Befehle auf einem zweiten Thread aus.\n\nDies Beschleunigt die Shader-Kompilierung, reduziert Stottern und verbessert die Leistung auf GPU-Treibern ohne eigene Multithreading-Unterstützung. Geringfügig bessere Leistung bei Treibern mit Multithreading.\n\nIm Zweifelsfall auf auf AUTO stellen.", + "ShaderCacheToggleTooltip": "Speichert einen persistenten Shader Cache, der das Stottern bei nachfolgenden Durchläufen reduziert.\n\nIm Zweifelsfall AN lassen.", + "ResolutionScaleTooltip": "Wendet die Auflösungsskalierung auf anwendbare Render Ziele", + "ResolutionScaleEntryTooltip": "Fließkomma Auflösungsskalierung, wie 1,5.\n Bei nicht ganzzahligen Werten ist die Wahrscheinlichkeit größer, dass Probleme entstehen, die auch zum Absturz führen können.", + "AnisotropyTooltip": "Stufe der Anisotropen Filterung (Auf Auto setzen, um den vom Spiel geforderten Wert zu verwenden)", + "AspectRatioTooltip": "Auf das Renderer-Fenster angewandtes Seitenverhältnis.", + "ShaderDumpPathTooltip": "Grafik-Shader-Dump-Pfad", + "FileLogTooltip": "Speichert die Konsolenausgabe in einer Log-Datei auf der Festplatte. Hat keinen Einfluss auf die Leistung.", + "StubLogTooltip": "Ausgabe von Stub-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", + "InfoLogTooltip": "Ausgabe von Info-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", + "WarnLogTooltip": "Ausgabe von Warn-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", + "ErrorLogTooltip": "Ausgabe von Fehler-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", + "TraceLogTooltip": "Ausgabe von Trace-Log in der Konsole. Hat keinen Einfluss auf die Leistung.", + "GuestLogTooltip": "Ausgabe von Gast-Logs in der Konsole. Hat keinen Einfluss auf die Leistung.", + "FileAccessLogTooltip": "Ausgabe von FS-Zugriff-Logs in der Konsole.", + "FSAccessLogModeTooltip": "Aktiviert die Ausgabe des FS-Zugriff-Logs in der Konsole. Mögliche Modi sind 0-3", + "DeveloperOptionTooltip": "Mit Vorsicht verwenden", + "OpenGlLogLevel": "Erfordert die Aktivierung der entsprechenden Log-Level", + "DebugLogTooltip": "Ausgabe von Debug-Logs in der Konsole.\n\nVerwende diese Option nur auf ausdrückliche Anweisung von Ryujinx Entwicklern, da sie das Lesen der Protokolle erschwert und die Leistung des Emulators verschlechtert.", + "LoadApplicationFileTooltip": "Öffnet die Dateiauswahl um Datei zu laden, welche mit der Switch kompatibel ist", + "LoadApplicationFolderTooltip": "Öffnet die Dateiauswahl um ein Spiel zu laden, welches mit der Switch kompatibel ist", + "OpenRyujinxFolderTooltip": "Öffnet den Ordner, der das Ryujinx Dateisystem enthält", + "OpenRyujinxLogsTooltip": "Öffnet den Ordner, in welchem die Logs gespeichert werden", + "ExitTooltip": "Beendet Ryujinx", + "OpenSettingsTooltip": "Öffnet das Einstellungsfenster", + "OpenProfileManagerTooltip": "Öffnet das Profilverwaltungsfenster", + "StopEmulationTooltip": "Beendet die Emulation des derzeitigen Spiels und kehrt zu der Spielauswahl zurück", + "CheckUpdatesTooltip": "Sucht nach Updates für Ryujinx", + "OpenAboutTooltip": "Öffnet das 'Über Ryujinx'-Fenster", + "GridSize": "Rastergröße", + "GridSizeTooltip": "Ändert die Größe der Rasterelemente", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brasilianisches Portugiesisch", + "AboutRyujinxContributorsButtonHeader": "Alle Mitwirkenden anzeigen", + "SettingsTabSystemAudioVolume": "Lautstärke: ", + "AudioVolumeTooltip": "Ändert die Lautstärke", + "SettingsTabSystemEnableInternetAccess": "Gast-Internet-Zugang/LAN Modus", + "EnableInternetAccessTooltip": "Erlaubt es der emulierten Anwendung sich mit dem Internet zu verbinden.\n\nSpiele die den LAN-Modus unterstützen, ermöglichen es Ryujinx sich sowohl mit anderen Ryujinx-Systemen, als auch mit offiziellen Nintendo Switch Konsolen zu verbinden. Allerdings nur, wenn diese Option aktiviert ist und die Systeme mit demselben lokalen Netzwerk verbunden sind.\n\nDies erlaubt KEINE Verbindung zu Nintendo-Servern. Kann bei bestimmten Spielen die versuchen sich mit dem Internet zu verbinden zum Absturz führen.\n\nIm Zweifelsfall AUS lassen", + "GameListContextMenuManageCheatToolTip": "Öffnet den Cheat-Manager", + "GameListContextMenuManageCheat": "Cheats verwalten", + "ControllerSettingsStickRange": "Bereich:", + "DialogStopEmulationTitle": "Ryujinx - Beende Emulation", + "DialogStopEmulationMessage": "Emulation wirklich beenden?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Audio", + "SettingsTabNetwork": "Netzwerk", + "SettingsTabNetworkConnection": "Netwerkverbindung", + "SettingsTabCpuCache": "CPU-Cache", + "SettingsTabCpuMemory": "CPU-Speicher", + "DialogUpdaterFlatpakNotSupportedMessage": "Bitte aktualisiere Ryujinx mit FlatHub", + "UpdaterDisabledWarningTitle": "Updater deaktiviert!", + "GameListContextMenuOpenSdModsDirectory": "Atmosphere-Mod-Verzeichnis öffnen", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Öffnet das alternative SD-Karten-Atmosphere-Verzeichnis, das die Mods der Anwendung enthält. Dieser Ordner ist nützlich für Mods, die für einen gemoddete Switch erstellt worden sind.", + "ControllerSettingsRotate90": "Um 90° rotieren", + "IconSize": "Cover Größe", + "IconSizeTooltip": "Ändert die Größe der Spiel-Cover", + "MenuBarOptionsShowConsole": "Zeige Konsole", + "ShaderCachePurgeError": "Es ist ein Fehler beim löschen des Shader Caches aufgetreten bei {0}: {1}", + "UserErrorNoKeys": "Keys nicht gefunden", + "UserErrorNoFirmware": "Firmware nicht gefunden", + "UserErrorFirmwareParsingFailed": "Firmware-Analysierung-Fehler", + "UserErrorApplicationNotFound": "Anwendung nicht gefunden", + "UserErrorUnknown": "Unbekannter Fehler", + "UserErrorUndefined": "Undefinierter Fehler", + "UserErrorNoKeysDescription": "Ryujinx konnte deine 'prod.keys' Datei nicht finden", + "UserErrorNoFirmwareDescription": "Ryujinx konnte keine installierte Firmware finden!", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx konnte die zu verfügung gestellte Firmware nicht analysieren. Ein möglicher Grund dafür sind veraltete keys.", + "UserErrorApplicationNotFoundDescription": "Ryujinx konnte keine valide Anwendung an dem gegeben Pfad finden.", + "UserErrorUnknownDescription": "Ein unbekannter Fehler ist aufgetreten!", + "UserErrorUndefinedDescription": "Ein undefinierter Fehler ist aufgetreten! Dies sollte nicht passieren. Bitte kontaktiere einen Entwickler!", + "OpenSetupGuideMessage": "Öffne den 'Setup Guide'", + "NoUpdate": "Kein Update", + "TitleUpdateVersionLabel": "Version {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Bestätigung", + "FileDialogAllTypes": "Alle Typen", + "Never": "Niemals", + "SwkbdMinCharacters": "Muss mindestens {0} Zeichen lang sein", + "SwkbdMinRangeCharacters": "Muss {0}-{1} Zeichen lang sein", + "SoftwareKeyboard": "Software-Tastatur", + "DialogControllerAppletMessagePlayerRange": "Die Anwendung benötigt {0} Spieler mit:\n\nTYPEN: {1}\n\nSPIELER: {2}\n\n{3}Bitte öffne die Einstellungen und rekonfiguriere die Controller Einstellungen oder drücke auf schließen.", + "DialogControllerAppletMessage": "Die Anwendung benötigt genau {0} Speieler mit:\n\nTYPEN: {1}\n\nSPIELER: {2}\n\n{3}Bitte öffne die Einstellungen und rekonfiguriere die Controller Einstellungen oder drücke auf schließen.", + "DialogControllerAppletDockModeSet": "Der 'Docked Modus' ist ausgewählt. Handheld ist ebenfalls ungültig.\n\n", + "UpdaterRenaming": "Alte Dateien umbenennen...", + "UpdaterRenameFailed": "Der Updater konnte die folgende Datei nicht umbenennen: {0}", + "UpdaterAddingFiles": "Neue Dateien hinzufügen...", + "UpdaterExtracting": "Update extrahieren...", + "UpdaterDownloading": "Update herunterladen...", + "Game": "Spiel", + "Docked": "Docked", + "Handheld": "Handheld", + "ConnectionError": "Verbindungsfehler.", + "AboutPageDeveloperListMore": "{0} und mehr...", + "ApiError": "API Fehler.", + "LoadingHeading": "{0} wird gestartet", + "CompilingPPTC": "PTC wird kompiliert", + "CompilingShaders": "Shader werden kompiliert", + "AllKeyboards": "Alle Tastaturen", + "OpenFileDialogTitle": "Wähle eine unterstützte Datei", + "OpenFolderDialogTitle": "Wähle einen Ordner mit einem entpackten Spiel", + "AllSupportedFormats": "Alle unterstützten Formate", + "RyujinxUpdater": "Ryujinx - Updater", + "SettingsTabHotkeys": "Tastatur Hotkeys", + "SettingsTabHotkeysHotkeys": "Tastatur Hotkeys", + "SettingsTabHotkeysToggleVsyncHotkey": "VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Screenshot:", + "SettingsTabHotkeysShowUiHotkey": "Zeige UI:", + "SettingsTabHotkeysPauseHotkey": "Pausieren:", + "SettingsTabHotkeysToggleMuteHotkey": "Stummschalten:", + "ControllerMotionTitle": "Bewegungssteuerung - Einstellungen", + "ControllerRumbleTitle": "Vibration - Einstellungen", + "SettingsSelectThemeFileDialogTitle": "Wähle ein Design für die Emulator-Benutzeroberfläche", + "SettingsXamlThemeFile": "Xaml Design-Datei", + "AvatarWindowTitle": "Profile verwalten - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Unbekannt", + "Usage": "Nutzung", + "Writable": "Beschreibbar", + "SelectDlcDialogTitle": "DLC-Dateien auswählen", + "SelectUpdateDialogTitle": "Update-Datei auswählen", + "UserProfileWindowTitle": "Benutzerprofile verwalten", + "CheatWindowTitle": "Spiel-Cheats verwalten", + "DlcWindowTitle": "Spiel-DLC verwalten", + "UpdateWindowTitle": "Spiel-Updates verwalten", + "CheatWindowHeading": "Cheats verfügbar für {0} [{1}]", + "DlcWindowHeading": "DLC verfügbar für {0} [{1}]", + "UserProfilesEditProfile": "Profil bearbeiten", + "Cancel": "Abbrechen", + "Save": "Speichern", + "Discard": "Verwerfen", + "UserProfilesSetProfileImage": "Profilbild einrichten", + "UserProfileEmptyNameError": "Name ist erforderlich", + "UserProfileNoImageError": "Bitte ein Profilbild auswählen", + "GameUpdateWindowHeading": "Update verfügbar für {0} [{1}]", + "SettingsTabHotkeysResScaleUpHotkey": "Auflösung erhöhen:", + "SettingsTabHotkeysResScaleDownHotkey": "Auflösung verringern:", + "UserProfilesName": "Name:", + "UserProfilesUserId": "Benutzer Id:", + "SettingsTabGraphicsBackend": "Grafik-Backend:", + "SettingsTabGraphicsBackendTooltip": "Verwendendetes Grafik-Backend", + "SettingsEnableTextureRecompression": "Textur-Rekompression", + "SettingsEnableTextureRecompressionTooltip": "Komprimiert bestimmte Texturen, um den VRAM-Verbrauch zu reduzieren.\n\nEmpfohlen für die Verwendung von GPUs, die weniger als 4 GiB VRAM haben.\n\nIm Zweifelsfall AUS lassen", + "SettingsTabGraphicsPreferredGpu": "Bevorzugte GPU:", + "SettingsTabGraphicsPreferredGpuTooltip": "Wähle die Grafikkarte aus, die mit dem Vulkan Grafik-Backend verwendet werden soll.\n\nDies hat keinen Einfluss auf die GPU die OpenGL verwendet.\n\nIm Zweifelsfall die als \"dGPU\" gekennzeichnete GPU auswählen. Diese Einstellung unberührt lassen, wenn keine zur Auswahl steht.", + "SettingsAppRequiredRestartMessage": "Ein Neustart von Ryujinx ist erforderlich", + "SettingsGpuBackendRestartMessage": "Das Grafik-Backend oder die Grafikkarteneinstellungen wurden geändert. Ein Neustart ist erforderlich um diese Einstellungen anzuwenden.", + "SettingsGpuBackendRestartSubMessage": "Ryujinx jetzt neu starten?", + "RyujinxUpdaterMessage": "Möchtest du Ryujinx auf die neueste Version aktualisieren?", + "SettingsTabHotkeysVolumeUpHotkey": "Lautstärke erhöhen:", + "SettingsTabHotkeysVolumeDownHotkey": "Lautstärke verringern:", + "SettingsEnableMacroHLE": "HLE Makros aktivieren", + "SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Speicherstände verwalten", + "DeleteUserSave": "Möchtest du den Spielerstand für dieses Spiel löschen?", + "IrreversibleActionNote": "Diese Option kann nicht rückgängig gemacht werden.", + "SaveManagerHeading": "Spielstände für {0} verwalten", + "SaveManagerTitle": "Speicherdaten Manager", + "Name": "Name", + "Size": "Größe", + "Search": "Suche", + "UserProfilesRecoverLostAccounts": "Konto wiederherstellen", + "Recover": "Wiederherstellen", + "UserProfilesRecoverHeading": "Speicherstände wurden für die folgenden Konten gefunden" +} diff --git a/src/Ryujinx.Ava/Assets/Locales/el_GR.json b/src/Ryujinx.Ava/Assets/Locales/el_GR.json new file mode 100644 index 00000000..de889736 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/el_GR.json @@ -0,0 +1,614 @@ +{ + "Language": "Ελληνικά", + "MenuBarFileOpenApplet": "Άνοιγμα Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Άνοιγμα του Mii Editor Applet σε Αυτόνομη λειτουργία", + "SettingsTabInputDirectMouseAccess": "Άμεση Πρόσβαση Ποντικιού", + "SettingsTabSystemMemoryManagerMode": "Λειτουργία Διαχείρισης Μνήμης:", + "SettingsTabSystemMemoryManagerModeSoftware": "Λογισμικό", + "SettingsTabSystemMemoryManagerModeHost": "Υπολογιστής (γρήγορο)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Χωρίς Ελέγχους (γρηγορότερο, μη ασφαλές)", + "MenuBarFile": "_Αρχείο", + "MenuBarFileOpenFromFile": "_Φόρτωση Αρχείου Εφαρμογής", + "MenuBarFileOpenUnpacked": "Φόρτωση Απακετάριστου _Παιχνιδιού", + "MenuBarFileOpenEmuFolder": "Άνοιγμα Φακέλου Ryujinx", + "MenuBarFileOpenLogsFolder": "Άνοιγμα Φακέλου Καταγραφής", + "MenuBarFileExit": "_Έξοδος", + "MenuBarOptions": "Επιλογές", + "MenuBarOptionsToggleFullscreen": "Λειτουργία Πλήρους Οθόνης", + "MenuBarOptionsStartGamesInFullscreen": "Εκκίνηση Παιχνιδιών σε Πλήρη Οθόνη", + "MenuBarOptionsStopEmulation": "Διακοπή Εξομοίωσης", + "MenuBarOptionsSettings": "_Ρυθμίσεις", + "MenuBarOptionsManageUserProfiles": "Διαχείριση Προφίλ _Χρηστών", + "MenuBarActions": "_Δράσεις", + "MenuBarOptionsSimulateWakeUpMessage": "Προσομοίωση Μηνύματος Αφύπνισης", + "MenuBarActionsScanAmiibo": "Σάρωση Amiibo", + "MenuBarTools": "Εργα_λεία", + "MenuBarToolsInstallFirmware": "Εγκατάσταση Firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Εγκατάσταση Firmware από XCI ή ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Εγκατάσταση Firmware από τοποθεσία", + "MenuBarHelp": "Βοήθεια", + "MenuBarHelpCheckForUpdates": "Έλεγχος για Ενημερώσεις", + "MenuBarHelpAbout": "Σχετικά με", + "MenuSearch": "Αναζήτηση...", + "GameListHeaderFavorite": "Αγαπημένο", + "GameListHeaderIcon": "Εικονίδιο", + "GameListHeaderApplication": "Όνομα", + "GameListHeaderDeveloper": "Προγραμματιστής", + "GameListHeaderVersion": "Έκδοση", + "GameListHeaderTimePlayed": "Χρόνος", + "GameListHeaderLastPlayed": "Παίχτηκε", + "GameListHeaderFileExtension": "Κατάληξη", + "GameListHeaderFileSize": "Μέγεθος", + "GameListHeaderPath": "Τοποθεσία", + "GameListContextMenuOpenUserSaveDirectory": "Άνοιγμα Τοποθεσίας Αποθήκευσης Χρήστη", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση Χρήστη της εφαρμογής", + "GameListContextMenuOpenDeviceSaveDirectory": "Άνοιγμα Τοποθεσίας Συσκευής Χρήστη", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση Συσκευής της εφαρμογής", + "GameListContextMenuOpenBcatSaveDirectory": "Άνοιγμα Τοποθεσίας BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την Αποθήκευση BCAT της εφαρμογής", + "GameListContextMenuManageTitleUpdates": "Διαχείριση Ενημερώσεων Παιχνιδιού", + "GameListContextMenuManageTitleUpdatesToolTip": "Ανοίγει το παράθυρο διαχείρισης Ενημερώσεων Παιχνιδιού", + "GameListContextMenuManageDlc": "Διαχείριση DLC", + "GameListContextMenuManageDlcToolTip": "Ανοίγει το παράθυρο διαχείρισης DLC", + "GameListContextMenuOpenModsDirectory": "Άνοιγμα Τοποθεσίας Τροποποιήσεων", + "GameListContextMenuOpenModsDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει τις Τροποποιήσεις της εφαρμογής", + "GameListContextMenuCacheManagement": "Διαχείριση Προσωρινής Μνήμης", + "GameListContextMenuCacheManagementPurgePptc": "Εκκαθάριση Προσωρινής Μνήμης PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Διαγράφει την προσωρινή μνήμη PPTC της εφαρμογής", + "GameListContextMenuCacheManagementPurgeShaderCache": "Εκκαθάριση Προσωρινής Μνήμης Shader", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Διαγράφει την προσωρινή μνήμη Shader της εφαρμογής", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Άνοιγμα Τοποθεσίας PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει τη προσωρινή μνήμη PPTC της εφαρμογής", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Άνοιγμα τοποθεσίας προσωρινής μνήμης Shader", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Ανοίγει την τοποθεσία που περιέχει την προσωρινή μνήμη Shader της εφαρμογής", + "GameListContextMenuExtractData": "Εξαγωγή Δεδομένων", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Εξαγωγή της ενότητας ExeFS από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Εξαγωγή της ενότητας RomFS από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)", + "GameListContextMenuExtractDataLogo": "Λογότυπο", + "GameListContextMenuExtractDataLogoToolTip": "Εξαγωγή της ενότητας Logo από την τρέχουσα διαμόρφωση της εφαρμογής (συμπεριλαμβανομένου ενημερώσεων)", + "StatusBarGamesLoaded": "{0}/{1} Φορτωμένα Παιχνίδια", + "StatusBarSystemVersion": "Έκδοση Συστήματος: {0}", + "Settings": "Ρυθμίσεις", + "SettingsTabGeneral": "Εμφάνιση", + "SettingsTabGeneralGeneral": "Γενικά", + "SettingsTabGeneralEnableDiscordRichPresence": "Ενεργοποίηση Εμπλουτισμένης Παρουσίας Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Έλεγχος για Ενημερώσεις στην Εκκίνηση", + "SettingsTabGeneralShowConfirmExitDialog": "Εμφάνιση διαλόγου \"Επιβεβαίωση Εξόδου\".", + "SettingsTabGeneralHideCursorOnIdle": "Απόκρυψη Δρομέα στην Αδράνεια", + "SettingsTabGeneralGameDirectories": "Τοποθεσίες παιχνιδιών", + "SettingsTabGeneralAdd": "Προσθήκη", + "SettingsTabGeneralRemove": "Αφαίρεση", + "SettingsTabSystem": "Σύστημα", + "SettingsTabSystemCore": "Πυρήνας", + "SettingsTabSystemSystemRegion": "Περιοχή Συστήματος:", + "SettingsTabSystemSystemRegionJapan": "Ιαπωνία", + "SettingsTabSystemSystemRegionUSA": "ΗΠΑ", + "SettingsTabSystemSystemRegionEurope": "Ευρώπη", + "SettingsTabSystemSystemRegionAustralia": "Αυστραλία", + "SettingsTabSystemSystemRegionChina": "Κίνα", + "SettingsTabSystemSystemRegionKorea": "Κορέα", + "SettingsTabSystemSystemRegionTaiwan": "Ταϊβάν", + "SettingsTabSystemSystemLanguage": "Γλώσσα Συστήματος:", + "SettingsTabSystemSystemLanguageJapanese": "Ιαπωνικά", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Αμερικάνικα Αγγλικά", + "SettingsTabSystemSystemLanguageFrench": "Γαλλικά", + "SettingsTabSystemSystemLanguageGerman": "Γερμανικά", + "SettingsTabSystemSystemLanguageItalian": "Ιταλικά", + "SettingsTabSystemSystemLanguageSpanish": "Ισπανικά", + "SettingsTabSystemSystemLanguageChinese": "Κινέζικα", + "SettingsTabSystemSystemLanguageKorean": "Κορεάτικα", + "SettingsTabSystemSystemLanguageDutch": "Ολλανδικά", + "SettingsTabSystemSystemLanguagePortuguese": "Πορτογαλικά", + "SettingsTabSystemSystemLanguageRussian": "Ρώσικα", + "SettingsTabSystemSystemLanguageTaiwanese": "Ταϊβανέζικα", + "SettingsTabSystemSystemLanguageBritishEnglish": "Βρετανικά Αγγλικά", + "SettingsTabSystemSystemLanguageCanadianFrench": "Καναδικά Γαλλικά", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Λατινοαμερικάνικα Ισπανικά", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Απλοποιημένα Κινέζικα", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Παραδοσιακά Κινεζικά", + "SettingsTabSystemSystemTimeZone": "Ζώνη Ώρας Συστήματος:", + "SettingsTabSystemSystemTime": "Ώρα Συστήματος:", + "SettingsTabSystemEnableVsync": "Ενεργοποίηση Κατακόρυφου Συγχρονισμού", + "SettingsTabSystemEnablePptc": "Ενεργοποίηση PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Ενεργοποίηση Ελέγχων Ακεραιότητας FS", + "SettingsTabSystemAudioBackend": "Backend Ήχου:", + "SettingsTabSystemAudioBackendDummy": "Απενεργοποιημένο", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Μικροδιορθώσεις", + "SettingsTabSystemHacksNote": " (Μπορεί να προκαλέσουν αστάθεια)", + "SettingsTabSystemExpandDramSize": "Επέκταση μεγέθους DRAM στα 6GiB", + "SettingsTabSystemIgnoreMissingServices": "Αγνόηση υπηρεσιών που λείπουν", + "SettingsTabGraphics": "Γραφικά", + "SettingsTabGraphicsAPI": "API Γραφικά", + "SettingsTabGraphicsEnableShaderCache": "Ενεργοποίηση Προσωρινής Μνήμης Shader", + "SettingsTabGraphicsAnisotropicFiltering": "Ανισότροπο Φιλτράρισμα:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Αυτόματο", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Κλίμακα Ανάλυσης:", + "SettingsTabGraphicsResolutionScaleCustom": "Προσαρμοσμένο (Δεν συνιστάται)", + "SettingsTabGraphicsResolutionScaleNative": "Εγγενής (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Αναλογία Απεικόνισης:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Έκταση σε όλο το παράθυρο", + "SettingsTabGraphicsDeveloperOptions": "Επιλογές Προγραμματιστή", + "SettingsTabGraphicsShaderDumpPath": "Τοποθεσία Shaders Γραφικών:", + "SettingsTabLogging": "Καταγραφή", + "SettingsTabLoggingLogging": "Καταγραφή", + "SettingsTabLoggingEnableLoggingToFile": "Ενεργοποίηση Καταγραφής Αρχείου", + "SettingsTabLoggingEnableStubLogs": "Ενεργοποίηση Καταγραφής Stub", + "SettingsTabLoggingEnableInfoLogs": "Ενεργοποίηση Καταγραφής Πληροφοριών", + "SettingsTabLoggingEnableWarningLogs": "Ενεργοποίηση Καταγραφής Προειδοποίησης", + "SettingsTabLoggingEnableErrorLogs": "Ενεργοποίηση Καταγραφής Σφαλμάτων", + "SettingsTabLoggingEnableTraceLogs": "Ενεργοποίηση Καταγραφής Ιχνών", + "SettingsTabLoggingEnableGuestLogs": "Ενεργοποίηση Καταγραφής Επισκεπτών", + "SettingsTabLoggingEnableFsAccessLogs": "Ενεργοποίηση Καταγραφής Πρόσβασης FS", + "SettingsTabLoggingFsGlobalAccessLogMode": "Λειτουργία Καταγραφής Καθολικής Πρόσβασης FS:", + "SettingsTabLoggingDeveloperOptions": "Επιλογές Προγραμματιστή (ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Η απόδοση Θα μειωθεί)", + "SettingsTabLoggingGraphicsBackendLogLevel": "Επίπεδο Καταγραφής Διεπαφής Γραφικών:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Κανένα", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Σφάλμα", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Επιβραδύνσεις", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Όλα", + "SettingsTabLoggingEnableDebugLogs": "Ενεργοποίηση Αρχείων Καταγραφής Εντοπισμού Σφαλμάτων", + "SettingsTabInput": "Χειρισμός", + "SettingsTabInputEnableDockedMode": "Ενεργοποίηση Docked Mode", + "SettingsTabInputDirectKeyboardAccess": "Άμεση Πρόσβαση στο Πληκτρολόγιο", + "SettingsButtonSave": "Αποθήκευση", + "SettingsButtonClose": "Κλείσιμο", + "SettingsButtonOk": "ΟΚ", + "SettingsButtonCancel": "Ακύρωση", + "SettingsButtonApply": "Εφαρμογή", + "ControllerSettingsPlayer": "Παίχτης", + "ControllerSettingsPlayer1": "Παίχτης 1", + "ControllerSettingsPlayer2": "Παίχτης 2", + "ControllerSettingsPlayer3": "Παίχτης 3", + "ControllerSettingsPlayer4": "Παίχτης 4", + "ControllerSettingsPlayer5": "Παίχτης 5", + "ControllerSettingsPlayer6": "Παίχτης 6", + "ControllerSettingsPlayer7": "Παίχτης 7", + "ControllerSettingsPlayer8": "Παίχτης 8", + "ControllerSettingsHandheld": "Χειροκίνητο", + "ControllerSettingsInputDevice": "Συσκευή Χειρισμού", + "ControllerSettingsRefresh": "Ανανέωση", + "ControllerSettingsDeviceDisabled": "Απενεργοποιημένο", + "ControllerSettingsControllerType": "Τύπος Χειριστηρίου", + "ControllerSettingsControllerTypeHandheld": "Φορητό", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "Ζεύγος JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "Αριστερό JoyCon", + "ControllerSettingsControllerTypeJoyConRight": "Δεξί JoyCon", + "ControllerSettingsProfile": "Προφίλ", + "ControllerSettingsProfileDefault": "Προκαθορισμένο", + "ControllerSettingsLoad": "Φόρτωση", + "ControllerSettingsAdd": "Προσθήκη", + "ControllerSettingsRemove": "Αφαίρεση", + "ControllerSettingsButtons": "Κουμπιά", + "ControllerSettingsButtonA": "Α", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Κατευθυντικό Pad", + "ControllerSettingsDPadUp": "Πάνω", + "ControllerSettingsDPadDown": "Κάτω", + "ControllerSettingsDPadLeft": "Αριστερά", + "ControllerSettingsDPadRight": "Δεξιά", + "ControllerSettingsLStick": "Αριστερός Μοχλός", + "ControllerSettingsLStickButton": "Κουμπί", + "ControllerSettingsLStickUp": "Πάνω", + "ControllerSettingsLStickDown": "Κάτω", + "ControllerSettingsLStickLeft": "Αριστερά", + "ControllerSettingsLStickRight": "Δεξιά", + "ControllerSettingsLStickStick": "Μοχλός", + "ControllerSettingsLStickInvertXAxis": "Αντιστροφή Μοχλού X", + "ControllerSettingsLStickInvertYAxis": "Αντιστροφή Μοχλού Y", + "ControllerSettingsLStickDeadzone": "Νεκρή Ζώνη:", + "ControllerSettingsRStick": "Δεξιός Μοχλός", + "ControllerSettingsRStickButton": "Κουμπί", + "ControllerSettingsRStickUp": "Πάνω", + "ControllerSettingsRStickDown": "Κάτω", + "ControllerSettingsRStickLeft": "Αριστερά", + "ControllerSettingsRStickRight": "Δεξιά", + "ControllerSettingsRStickStick": "Μοχλός", + "ControllerSettingsRStickInvertXAxis": "Αντιστροφή Μοχλού X", + "ControllerSettingsRStickInvertYAxis": "Αντιστροφή Μοχλού Y", + "ControllerSettingsRStickDeadzone": "Νεκρή Ζώνη:", + "ControllerSettingsTriggersLeft": "Αριστερή Σκανδάλη", + "ControllerSettingsTriggersRight": "Δεξιά Σκανδάλη", + "ControllerSettingsTriggersButtonsLeft": "Αριστερά Κουμπιά Σκανδάλης", + "ControllerSettingsTriggersButtonsRight": "Δεξιά Κουμπιά Σκανδάλης", + "ControllerSettingsTriggers": "Σκανδάλες", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Αριστερά Κουμπιά", + "ControllerSettingsExtraButtonsRight": "Δεξιά Κουμπιά", + "ControllerSettingsMisc": "Διάφορα", + "ControllerSettingsTriggerThreshold": "Κατώφλι Σκανδάλης:", + "ControllerSettingsMotion": "Κίνηση", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Κίνηση συμβατή με CemuHook", + "ControllerSettingsMotionControllerSlot": "Υποδοχή Χειριστηρίου:", + "ControllerSettingsMotionMirrorInput": "Καθρεπτισμός Χειρισμού", + "ControllerSettingsMotionRightJoyConSlot": "Δεξιά Υποδοχή JoyCon:", + "ControllerSettingsMotionServerHost": "Κεντρικός Υπολογιστής Διακομιστή:", + "ControllerSettingsMotionGyroSensitivity": "Ευαισθησία Γυροσκοπίου:", + "ControllerSettingsMotionGyroDeadzone": "Νεκρή Ζώνη Γυροσκοπίου:", + "ControllerSettingsSave": "Αποθήκευση", + "ControllerSettingsClose": "Κλείσιμο", + "UserProfilesSelectedUserProfile": "Επιλεγμένο Προφίλ Χρήστη:", + "UserProfilesSaveProfileName": "Αποθήκευση Ονόματος Προφίλ", + "UserProfilesChangeProfileImage": "Αλλαγή Εικόνας Προφίλ", + "UserProfilesAvailableUserProfiles": "Διαθέσιμα Προφίλ Χρηστών:", + "UserProfilesAddNewProfile": "Προσθήκη Νέου Προφίλ", + "UserProfilesDelete": "Διαγράφω", + "UserProfilesClose": "Κλείσιμο", + "ProfileImageSelectionTitle": "Επιλογή Εικόνας Προφίλ", + "ProfileImageSelectionHeader": "Επιλέξτε μία Εικόνα Προφίλ", + "ProfileImageSelectionNote": "Μπορείτε να εισαγάγετε μία προσαρμοσμένη εικόνα προφίλ ή να επιλέξετε ένα avatar από το Firmware", + "ProfileImageSelectionImportImage": "Εισαγωγή Αρχείου Εικόνας", + "ProfileImageSelectionSelectAvatar": "Επιλέξτε Avatar από Firmware", + "InputDialogTitle": "Διάλογος Εισαγωγής", + "InputDialogOk": "ΟΚ", + "InputDialogCancel": "Ακύρωση", + "InputDialogAddNewProfileTitle": "Επιλογή Ονόματος Προφίλ", + "InputDialogAddNewProfileHeader": "Εισαγωγή Ονόματος Προφίλ", + "InputDialogAddNewProfileSubtext": "(Σύνολο Χαρακτήρων: {0})", + "AvatarChoose": "Επιλογή", + "AvatarSetBackgroundColor": "Ορισμός Χρώματος Φόντου", + "AvatarClose": "Κλείσιμο", + "ControllerSettingsLoadProfileToolTip": "Φόρτωση Προφίλ", + "ControllerSettingsAddProfileToolTip": "Προσθήκη Προφίλ", + "ControllerSettingsRemoveProfileToolTip": "Κατάργηση Προφίλ", + "ControllerSettingsSaveProfileToolTip": "Αποθήκευση Προφίλ", + "MenuBarFileToolsTakeScreenshot": "Λήψη Στιγμιότυπου", + "MenuBarFileToolsHideUi": "Απόκρυψη UI", + "GameListContextMenuToggleFavorite": "Εναλλαγή Αγαπημένου", + "GameListContextMenuToggleFavoriteToolTip": "Εναλλαγή της Κατάστασης Αγαπημένο του Παιχνιδιού", + "SettingsTabGeneralTheme": "Θέμα", + "SettingsTabGeneralThemeCustomTheme": "Προσαρμοσμένη Τοποθεσία Θέματος", + "SettingsTabGeneralThemeBaseStyle": "Βασικό Στυλ", + "SettingsTabGeneralThemeBaseStyleDark": "Σκούρο", + "SettingsTabGeneralThemeBaseStyleLight": "Ανοιχτό", + "SettingsTabGeneralThemeEnableCustomTheme": "Ενεργοποίηση Προσαρμοσμένου Θέματος", + "ButtonBrowse": "Αναζήτηση", + "ControllerSettingsConfigureGeneral": "Παραμέτρων", + "ControllerSettingsRumble": "Δόνηση", + "ControllerSettingsRumbleStrongMultiplier": "Ισχυρός Πολλαπλασιαστής Δόνησης", + "ControllerSettingsRumbleWeakMultiplier": "Αδύναμος Πολλαπλασιαστής Δόνησης", + "DialogMessageSaveNotAvailableMessage": "Δεν υπάρχουν αποθηκευμένα δεδομένα για το {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Θέλετε να αποθηκεύσετε δεδομένα για αυτό το παιχνίδι;", + "DialogConfirmationTitle": "Ryujinx - Επιβεβαίωση", + "DialogUpdaterTitle": "Ryujinx - Ενημερωτής", + "DialogErrorTitle": "Ryujinx - Σφάλμα", + "DialogWarningTitle": "Ryujinx - Προειδοποίηση", + "DialogExitTitle": "Ryujinx - Έξοδος", + "DialogErrorMessage": "Το Ryujinx αντιμετώπισε σφάλμα", + "DialogExitMessage": "Είστε βέβαιοι ότι θέλετε να κλείσετε το Ryujinx;", + "DialogExitSubMessage": "Όλα τα μη αποθηκευμένα δεδομένα θα χαθούν!", + "DialogMessageCreateSaveErrorMessage": "Σφάλμα κατά τη δημιουργία των αποθηκευμένων δεδομένων: {0}", + "DialogMessageFindSaveErrorMessage": "Σφάλμα κατά την εύρεση των αποθηκευμένων δεδομένων: {0}", + "FolderDialogExtractTitle": "Επιλέξτε τον φάκελο στον οποίο θέλετε να εξαγάγετε", + "DialogNcaExtractionMessage": "Εξαγωγή ενότητας {0} από {1}...", + "DialogNcaExtractionTitle": "Ryujinx - NCA Εξαγωγέας Τμημάτων", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Αποτυχία εξαγωγής. Η κύρια NCA δεν υπήρχε στο επιλεγμένο αρχείο.", + "DialogNcaExtractionCheckLogErrorMessage": "Αποτυχία εξαγωγής. Διαβάστε το αρχείο καταγραφής για περισσότερες πληροφορίες.", + "DialogNcaExtractionSuccessMessage": "Η εξαγωγή ολοκληρώθηκε με επιτυχία.", + "DialogUpdaterConvertFailedMessage": "Αποτυχία μετατροπής της τρέχουσας έκδοσης Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Ακύρωση Ενημέρωσης!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Χρησιμοποιείτε ήδη την πιο ενημερωμένη έκδοση του Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Προέκυψε ένα σφάλμα στη λήψη πληροφοριών έκδοσης από τα GitHub Releases. Αυτό δύναται να συμβεί αν μία έκδοση χτίζεται αυτή τη στιγμή στα GitHub Actions. Παρακαλούμε προσπαθήστε αργότερα.", + "DialogUpdaterConvertFailedGithubMessage": "Αποτυχία μετατροπής της ληφθείσας έκδοσης Ryujinx από την έκδοση GitHub.", + "DialogUpdaterDownloadingMessage": "Λήψη Ενημέρωσης...", + "DialogUpdaterExtractionMessage": "Εξαγωγή Ενημέρωσης...", + "DialogUpdaterRenamingMessage": "Μετονομασία Ενημέρωσης...", + "DialogUpdaterAddingFilesMessage": "Προσθήκη Νέας Ενημέρωσης...", + "DialogUpdaterCompleteMessage": "Η Ενημέρωση Ολοκληρώθηκε!", + "DialogUpdaterRestartMessage": "Θέλετε να επανεκκινήσετε το Ryujinx τώρα;", + "DialogUpdaterArchNotSupportedMessage": "Δεν υπάρχει υποστηριζόμενη αρχιτεκτονική συστήματος!", + "DialogUpdaterArchNotSupportedSubMessage": "(Υποστηρίζονται μόνο συστήματα x64!)", + "DialogUpdaterNoInternetMessage": "Δεν είστε συνδεδεμένοι στο Διαδίκτυο!", + "DialogUpdaterNoInternetSubMessage": "Επαληθεύστε ότι έχετε σύνδεση στο Διαδίκτυο που λειτουργεί!", + "DialogUpdaterDirtyBuildMessage": "Δεν μπορείτε να ενημερώσετε μία Πρόχειρη Έκδοση του Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Κάντε λήψη του Ryujinx στη διεύθυνση https://ryujinx.org/ εάν αναζητάτε μία υποστηριζόμενη έκδοση.", + "DialogRestartRequiredMessage": "Απαιτείται Επανεκκίνηση", + "DialogThemeRestartMessage": "Το θέμα έχει αποθηκευτεί. Απαιτείται επανεκκίνηση για την εφαρμογή του θέματος.", + "DialogThemeRestartSubMessage": "Θέλετε να κάνετε επανεκκίνηση", + "DialogFirmwareInstallEmbeddedMessage": "Θα θέλατε να εγκαταστήσετε το Firmware που είναι ενσωματωμένο σε αυτό το παιχνίδι; (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Δεν βρέθηκε εγκατεστημένο Firmware, αλλά το Ryujinx μπόρεσε να εγκαταστήσει το Firmware {0} από το παρεχόμενο παιχνίδι.\nΟ εξομοιωτής θα ξεκινήσει τώρα.", + "DialogFirmwareNoFirmwareInstalledMessage": "Δεν έχει εγκατασταθεί Firmware", + "DialogFirmwareInstalledMessage": "Το Firmware {0} εγκαταστάθηκε", + "DialogOpenSettingsWindowLabel": "Άνοιγμα Παραθύρου Ρυθμίσεων", + "DialogControllerAppletTitle": "Applet Χειρισμού", + "DialogMessageDialogErrorExceptionMessage": "Σφάλμα εμφάνισης του διαλόγου Μηνυμάτων: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Σφάλμα εμφάνισης Λογισμικού Πληκτρολογίου: {0}", + "DialogErrorAppletErrorExceptionMessage": "Σφάλμα εμφάνισης του διαλόγου ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nΓια πληροφορίες σχετικά με τον τρόπο διόρθωσης του σφάλματος, ακολουθήστε τον Οδηγό Εγκατάστασης.", + "DialogUserErrorDialogTitle": "Σφάλμα Ryujinx ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Παρουσιάστηκε σφάλμα κατά την ανάκτηση πληροφοριών από το API.", + "DialogAmiiboApiConnectErrorMessage": "Δεν είναι δυνατή η σύνδεση με τον διακομιστή Amiibo API. Η υπηρεσία μπορεί να είναι εκτός λειτουργίας ή μπορεί να χρειαστεί να επαληθεύσετε ότι έχετε ενεργή σύνδεσή στο Διαδίκτυο.", + "DialogProfileInvalidProfileErrorMessage": "Το προφίλ {0} δεν είναι συμβατό με το τρέχον σύστημα χειρισμού.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Το προεπιλεγμένο προφίλ δεν μπορεί να αντικατασταθεί", + "DialogProfileDeleteProfileTitle": "Διαγραφή Προφίλ", + "DialogProfileDeleteProfileMessage": "Αυτή η ενέργεια είναι μη αναστρέψιμη, είστε βέβαιοι ότι θέλετε να συνεχίσετε;", + "DialogWarning": "Προειδοποίηση", + "DialogPPTCDeletionMessage": "Πρόκειται να διαγράψετε την προσωρινή μνήμη PPTC για :\n\n{0}\n\nΕίστε βέβαιοι ότι θέλετε να συνεχίσετε;", + "DialogPPTCDeletionErrorMessage": "Σφάλμα κατά την εκκαθάριση προσωρινής μνήμης PPTC στο {0}: {1}", + "DialogShaderDeletionMessage": "Πρόκειται να διαγράψετε την προσωρινή μνήμη Shader για :\n\n{0}\n\nΕίστε βέβαιοι ότι θέλετε να συνεχίσετε;", + "DialogShaderDeletionErrorMessage": "Σφάλμα κατά την εκκαθάριση προσωρινής μνήμης Shader στο {0}: {1}", + "DialogRyujinxErrorMessage": "Το Ryujinx αντιμετώπισε σφάλμα", + "DialogInvalidTitleIdErrorMessage": "Σφάλμα UI: Το επιλεγμένο παιχνίδι δεν έχει έγκυρο αναγνωριστικό τίτλου", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Δεν βρέθηκε έγκυρο Firmware συστήματος στο {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Εγκατάσταση Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Θα εγκατασταθεί η έκδοση συστήματος {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nΑυτό θα αντικαταστήσει την τρέχουσα έκδοση συστήματος {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nΘέλετε να συνεχίσετε;", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Εγκατάσταση Firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Η έκδοση συστήματος {0} εγκαταστάθηκε με επιτυχία.", + "DialogUserProfileDeletionWarningMessage": "Δεν θα υπάρχουν άλλα προφίλ εάν διαγραφεί το επιλεγμένο", + "DialogUserProfileDeletionConfirmMessage": "Θέλετε να διαγράψετε το επιλεγμένο προφίλ", + "DialogControllerSettingsModifiedConfirmMessage": "Οι τρέχουσες ρυθμίσεις χειρισμού έχουν ενημερωθεί.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Θέλετε να αποθηκεύσετε;", + "DialogLoadNcaErrorMessage": "{0}. Σφάλμα Αρχείου: {1}", + "DialogDlcNoDlcErrorMessage": "Το αρχείο δεν περιέχει DLC για τον επιλεγμένο τίτλο!", + "DialogPerformanceCheckLoggingEnabledMessage": "Έχετε ενεργοποιημένη την καταγραφή εντοπισμού σφαλμάτων, η οποία έχει σχεδιαστεί για χρήση μόνο από προγραμματιστές.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Για βέλτιστη απόδοση, συνιστάται η απενεργοποίηση καταγραφής εντοπισμού σφαλμάτων. Θέλετε να απενεργοποιήσετε την καταγραφή τώρα;", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Έχετε ενεργοποιήσει το Shader Dumping, το οποίο έχει σχεδιαστεί για χρήση μόνο από προγραμματιστές.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Για βέλτιστη απόδοση, συνιστάται να απενεργοποιήσετε το Shader Dumping. Θέλετε να απενεργοποιήσετε τώρα το Shader Dumping;", + "DialogLoadAppGameAlreadyLoadedMessage": "Ένα παιχνίδι έχει ήδη φορτωθεί", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Σταματήστε την εξομοίωση ή κλείστε τον εξομοιωτή πριν ξεκινήσετε ένα άλλο παιχνίδι.", + "DialogUpdateAddUpdateErrorMessage": "Το αρχείο δεν περιέχει ενημέρωση για τον επιλεγμένο τίτλο!", + "DialogSettingsBackendThreadingWarningTitle": "Προειδοποίηση - Backend Threading", + "DialogSettingsBackendThreadingWarningMessage": "Το Ryujinx πρέπει να επανεκκινηθεί αφού αλλάξει αυτή η επιλογή για να εφαρμοστεί πλήρως. Ανάλογα με την πλατφόρμα σας, μπορεί να χρειαστεί να απενεργοποιήσετε με μη αυτόματο τρόπο το multithreading του ίδιου του προγράμματος οδήγησης όταν χρησιμοποιείτε το Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Χαρακτηριστικά", + "SettingsTabGraphicsBackendMultithreading": "Πολυνηματική Επεξεργασία Γραφικών:", + "CommonAuto": "Αυτόματο", + "CommonOff": "Ανενεργό", + "CommonOn": "Ενεργό", + "InputDialogYes": "Ναί", + "InputDialogNo": "Όχι", + "DialogProfileInvalidProfileNameErrorMessage": "Το όνομα αρχείου περιέχει μη έγκυρους χαρακτήρες. Παρακαλώ προσπαθήστε ξανά.", + "MenuBarOptionsPauseEmulation": "Παύση", + "MenuBarOptionsResumeEmulation": "Συνέχιση", + "AboutUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τον ιστότοπο Ryujinx στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutDisclaimerMessage": "Το Ryujinx δεν είναι συνδεδεμένο με τη Nintendo™,\nούτε με κανέναν από τους συνεργάτες της, με οποιονδήποτε τρόπο.", + "AboutAmiiboDisclaimerMessage": "Το AmiiboAPI (www.amiiboapi.com) χρησιμοποιείται\nστην προσομοίωση Amiibo.", + "AboutPatreonUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Ryujinx Patreon στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutGithubUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Ryujinx GitHub στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutDiscordUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε μία πρόσκληση στον διακομιστή Ryujinx Discord στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutTwitterUrlTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Ryujinx Twitter στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutRyujinxAboutTitle": "Σχετικά με:", + "AboutRyujinxAboutContent": "Το Ryujinx είναι ένας εξομοιωτής για το Nintendo Switch™.\nΥποστηρίξτε μας στο Patreon.\nΛάβετε όλα τα τελευταία νέα στο Twitter ή στο Discord.\nΟι προγραμματιστές που ενδιαφέρονται να συνεισφέρουν μπορούν να μάθουν περισσότερα στο GitHub ή στο Discord μας.", + "AboutRyujinxMaintainersTitle": "Συντηρείται από:", + "AboutRyujinxMaintainersContentTooltipMessage": "Κάντε κλικ για να ανοίξετε τη σελίδα Συνεισφέροντες στο προεπιλεγμένο πρόγραμμα περιήγησης.", + "AboutRyujinxSupprtersTitle": "Υποστηρίζεται στο Patreon από:", + "AmiiboSeriesLabel": "Σειρά Amiibo", + "AmiiboCharacterLabel": "Χαρακτήρας", + "AmiiboScanButtonLabel": "Σαρώστε το", + "AmiiboOptionsShowAllLabel": "Εμφάνιση όλων των Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack: Χρησιμοποιήστε τυχαίο αναγνωριστικό UUID", + "DlcManagerTableHeadingEnabledLabel": "Ενεργοποιήθηκε", + "DlcManagerTableHeadingTitleIdLabel": "Αναγνωριστικό τίτλου", + "DlcManagerTableHeadingContainerPathLabel": "Τοποθεσία DLC", + "DlcManagerTableHeadingFullPathLabel": "Πλήρης τοποθεσία", + "DlcManagerRemoveAllButton": "Αφαίρεση όλων", + "DlcManagerEnableAllButton": "Ενεργοποίηση Όλων", + "DlcManagerDisableAllButton": "Απενεργοποίηση Όλων", + "MenuBarOptionsChangeLanguage": "Αλλαξε γλώσσα", + "CommonSort": "Κατάταξη", + "CommonShowNames": "Εμφάνιση ονομάτων", + "CommonFavorite": "Αγαπημένα", + "OrderAscending": "Αύξουσα", + "OrderDescending": "Φθίνουσα", + "SettingsTabGraphicsFeatures": "Χαρακτηριστικά & Βελτιώσεις", + "ErrorWindowTitle": "Παράθυρο σφάλματος", + "ToggleDiscordTooltip": "Ενεργοποιεί ή απενεργοποιεί την Εμπλουτισμένη Παρουσία σας στο Discord", + "AddGameDirBoxTooltip": "Εισαγάγετε μία τοποθεσία παιχνιδιών για προσθήκη στη λίστα", + "AddGameDirTooltip": "Προσθέστε μία τοποθεσία παιχνιδιών στη λίστα", + "RemoveGameDirTooltip": "Αφαιρέστε την επιλεγμένη τοποθεσία παιχνιδιών", + "CustomThemeCheckTooltip": "Ενεργοποίηση ή απενεργοποίηση προσαρμοσμένων θεμάτων στο GUI", + "CustomThemePathTooltip": "Διαδρομή προς το προσαρμοσμένο θέμα GUI", + "CustomThemeBrowseTooltip": "Αναζητήστε ένα προσαρμοσμένο θέμα GUI", + "DockModeToggleTooltip": "Ενεργοποιήστε ή απενεργοποιήστε τη λειτουργία σύνδεσης", + "DirectKeyboardTooltip": "Ενεργοποίηση ή απενεργοποίηση της \"υποστήριξης άμεσης πρόσβασης πληκτρολογίου (HID)\" (Παρέχει πρόσβαση στα παιχνίδια στο πληκτρολόγιό σας ως συσκευή εισαγωγής κειμένου)", + "DirectMouseTooltip": "Ενεργοποίηση ή απενεργοποίηση της \"υποστήριξης άμεσης πρόσβασης ποντικιού (HID)\" (Παρέχει πρόσβαση στα παιχνίδια στο ποντίκι σας ως συσκευή κατάδειξης)", + "RegionTooltip": "Αλλαγή Περιοχής Συστήματος", + "LanguageTooltip": "Αλλαγή Γλώσσας Συστήματος", + "TimezoneTooltip": "Αλλαγή Ζώνης Ώρας Συστήματος", + "TimeTooltip": "Αλλαγή Ώρας Συστήματος", + "VSyncToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί τον κατακόρυφο συγχρονισμό", + "PptcToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί το PPTC", + "FsIntegrityToggleTooltip": "Ενεργοποιεί τους ελέγχους ακεραιότητας σε αρχεία περιεχομένου παιχνιδιού", + "AudioBackendTooltip": "Αλλαγή ήχου υποστήριξης", + "MemoryManagerTooltip": "Αλλάξτε τον τρόπο αντιστοίχισης και πρόσβασης στη μνήμη επισκέπτη. Επηρεάζει σε μεγάλο βαθμό την απόδοση της προσομοίωσης της CPU.", + "MemoryManagerSoftwareTooltip": "Χρησιμοποιήστε έναν πίνακα σελίδων λογισμικού για τη μετάφραση διευθύνσεων. Υψηλότερη ακρίβεια αλλά πιο αργή απόδοση.", + "MemoryManagerHostTooltip": "Απευθείας αντιστοίχιση της μνήμης στον χώρο διευθύνσεων υπολογιστή υποδοχής. Πολύ πιο γρήγορη μεταγλώττιση και εκτέλεση JIT.", + "MemoryManagerUnsafeTooltip": "Απευθείας χαρτογράφηση της μνήμης, αλλά μην καλύπτετε τη διεύθυνση εντός του χώρου διευθύνσεων επισκέπτη πριν από την πρόσβαση. Πιο γρήγορα, αλλά με κόστος ασφάλειας. Η εφαρμογή μπορεί να έχει πρόσβαση στη μνήμη από οπουδήποτε στο Ryujinx, επομένως εκτελείτε μόνο προγράμματα που εμπιστεύεστε με αυτήν τη λειτουργία.", + "DRamTooltip": "Επεκτείνει την ποσότητα της μνήμης στο εξομοιούμενο σύστημα από 4 GiB σε 6 GiB", + "IgnoreMissingServicesTooltip": "Ενεργοποίηση ή απενεργοποίηση της αγνοώησης για υπηρεσίες που λείπουν", + "GraphicsBackendThreadingTooltip": "Ενεργοποίηση Πολυνηματικής Επεξεργασίας Γραφικών", + "GalThreadingTooltip": "Εκτελεί εντολές γραφικών σε ένα δεύτερο νήμα. Επιτρέπει την πολυνηματική μεταγλώττιση Shader σε χρόνο εκτέλεσης, μειώνει το τρεμόπαιγμα και βελτιώνει την απόδοση των προγραμμάτων οδήγησης χωρίς τη δική τους υποστήριξη πολλαπλών νημάτων. Ποικίλες κορυφαίες επιδόσεις σε προγράμματα οδήγησης με multithreading. Μπορεί να χρειαστεί επανεκκίνηση του Ryujinx για να απενεργοποιήσετε σωστά την ενσωματωμένη λειτουργία πολλαπλών νημάτων του προγράμματος οδήγησης ή ίσως χρειαστεί να το κάνετε χειροκίνητα για να έχετε την καλύτερη απόδοση.", + "ShaderCacheToggleTooltip": "Ενεργοποιεί ή απενεργοποιεί την Προσωρινή Μνήμη Shader", + "ResolutionScaleTooltip": "Κλίμακα ανάλυσης που εφαρμόστηκε σε ισχύοντες στόχους απόδοσης", + "ResolutionScaleEntryTooltip": "Κλίμακα ανάλυσης κινητής υποδιαστολής, όπως 1,5. Οι μη αναπόσπαστες τιμές είναι πιθανό να προκαλέσουν προβλήματα ή σφάλματα.", + "AnisotropyTooltip": "Επίπεδο Ανισότροπου Φιλτραρίσματος (ρυθμίστε το στο Αυτόματο για να χρησιμοποιηθεί η τιμή που ζητήθηκε από το παιχνίδι)", + "AspectRatioTooltip": "Λόγος διαστάσεων που εφαρμόστηκε στο παράθυρο απόδοσης.", + "ShaderDumpPathTooltip": "Τοποθεσία Εναπόθεσης Προσωρινής Μνήμης Shaders", + "FileLogTooltip": "Ενεργοποιεί ή απενεργοποιεί την καταγραφή σε ένα αρχείο στο δίσκο", + "StubLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής ατελειών", + "InfoLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής πληροφοριών", + "WarnLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής προειδοποιήσεων", + "ErrorLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής σφαλμάτων", + "TraceLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής ιχνών", + "GuestLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων καταγραφής επισκεπτών", + "FileAccessLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής πρόσβασης", + "FSAccessLogModeTooltip": "Ενεργοποιεί την έξοδο καταγραφής πρόσβασης FS στην κονσόλα. Οι πιθανοί τρόποι λειτουργίας είναι 0-3", + "DeveloperOptionTooltip": "Χρησιμοποιήστε με προσοχή", + "OpenGlLogLevel": "Απαιτεί τα κατάλληλα επίπεδα καταγραφής ενεργοποιημένα", + "DebugLogTooltip": "Ενεργοποιεί την εκτύπωση μηνυμάτων αρχείου καταγραφής εντοπισμού σφαλμάτων", + "LoadApplicationFileTooltip": "Ανοίξτε έναν επιλογέα αρχείων για να επιλέξετε ένα αρχείο συμβατό με το Switch για φόρτωση", + "LoadApplicationFolderTooltip": "Ανοίξτε έναν επιλογέα αρχείων για να επιλέξετε μία μη συσκευασμένη εφαρμογή, συμβατή με το Switch για φόρτωση", + "OpenRyujinxFolderTooltip": "Ανοίξτε το φάκελο συστήματος αρχείων Ryujinx", + "OpenRyujinxLogsTooltip": "Ανοίξτε το φάκελο στον οποίο διατηρούνται τα αρχεία καταγραφής", + "ExitTooltip": "Έξοδος από το Ryujinx", + "OpenSettingsTooltip": "Ανοίξτε το παράθυρο Ρυθμίσεων", + "OpenProfileManagerTooltip": "Ανοίξτε το παράθυρο Διαχείρισης Προφίλ Χρήστη", + "StopEmulationTooltip": "Σταματήστε την εξομοίωση του τρέχοντος παιχνιδιού και επιστρέψτε στην επιλογή παιχνιδιού", + "CheckUpdatesTooltip": "Ελέγξτε για ενημερώσεις του Ryujinx", + "OpenAboutTooltip": "Ανοίξτε το Παράθυρο Σχετικά", + "GridSize": "Μέγεθος Πλέγματος", + "GridSizeTooltip": "Αλλαγή μεγέθους στοιχείων πλέγματος", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Πορτογαλικά Βραζιλίας", + "AboutRyujinxContributorsButtonHeader": "Δείτε Όλους τους Συντελεστές", + "SettingsTabSystemAudioVolume": "Ενταση Ήχου: ", + "AudioVolumeTooltip": "Αλλαγή Έντασης Ήχου", + "SettingsTabSystemEnableInternetAccess": "Ενεργοποίηση πρόσβασης επισκέπτη στο Διαδίκτυο", + "EnableInternetAccessTooltip": "Επιτρέπει την πρόσβαση επισκέπτη στο Διαδίκτυο. Εάν ενεργοποιηθεί, η εξομοιωμένη κονσόλα Switch θα συμπεριφέρεται σαν να είναι συνδεδεμένη στο Διαδίκτυο. Λάβετε υπόψη ότι σε ορισμένες περιπτώσεις, οι εφαρμογές ενδέχεται να εξακολουθούν να έχουν πρόσβαση στο Διαδίκτυο, ακόμη και όταν αυτή η επιλογή είναι απενεργοποιημένη", + "GameListContextMenuManageCheatToolTip": "Διαχείριση Κόλπων", + "GameListContextMenuManageCheat": "Διαχείριση Κόλπων", + "ControllerSettingsStickRange": "Εύρος:", + "DialogStopEmulationTitle": "Ryujinx - Διακοπή εξομοίωσης", + "DialogStopEmulationMessage": "Είστε βέβαιοι ότι θέλετε να σταματήσετε την εξομοίωση;", + "SettingsTabCpu": "Επεξεργαστής", + "SettingsTabAudio": "Ήχος", + "SettingsTabNetwork": "Δίκτυο", + "SettingsTabNetworkConnection": "Σύνδεση δικτύου", + "SettingsTabCpuCache": "Προσωρινή Μνήμη CPU", + "SettingsTabCpuMemory": "Μνήμη CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Παρακαλούμε ενημερώστε το Ryujinx μέσω FlatHub.", + "UpdaterDisabledWarningTitle": "Ο Διαχειριστής Ενημερώσεων Είναι Απενεργοποιημένος!", + "GameListContextMenuOpenSdModsDirectory": "Άνοιγμα Της Τοποθεσίας Των Atmosphere Mods", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", + "ControllerSettingsRotate90": "Rotate 90° Clockwise", + "IconSize": "Icon Size", + "IconSizeTooltip": "Change the size of game icons", + "MenuBarOptionsShowConsole": "Show Console", + "ShaderCachePurgeError": "Error purging shader cache at {0}: {1}", + "UserErrorNoKeys": "Keys not found", + "UserErrorNoFirmware": "Firmware not found", + "UserErrorFirmwareParsingFailed": "Firmware parsing error", + "UserErrorApplicationNotFound": "Application not found", + "UserErrorUnknown": "Unknown error", + "UserErrorUndefined": "Undefined error", + "UserErrorNoKeysDescription": "Ryujinx was unable to find your 'prod.keys' file", + "UserErrorNoFirmwareDescription": "Ryujinx was unable to find any firmwares installed", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx was unable to parse the provided firmware. This is usually caused by outdated keys.", + "UserErrorApplicationNotFoundDescription": "Ryujinx couldn't find a valid application at the given path.", + "UserErrorUnknownDescription": "An unknown error occured!", + "UserErrorUndefinedDescription": "An undefined error occured! This shouldn't happen, please contact a dev!", + "OpenSetupGuideMessage": "Open the Setup Guide", + "NoUpdate": "No Update", + "TitleUpdateVersionLabel": "Version {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Confirmation", + "FileDialogAllTypes": "All types", + "Never": "Never", + "SwkbdMinCharacters": "Must be at least {0} characters long", + "SwkbdMinRangeCharacters": "Must be {0}-{1} characters long", + "SoftwareKeyboard": "Software Keyboard", + "DialogControllerAppletMessagePlayerRange": "Application requests {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.", + "DialogControllerAppletMessage": "Application requests exactly {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.", + "DialogControllerAppletDockModeSet": "Docked mode set. Handheld is also invalid.\n\n", + "UpdaterRenaming": "Renaming Old Files...", + "UpdaterRenameFailed": "Updater was unable to rename file: {0}", + "UpdaterAddingFiles": "Adding New Files...", + "UpdaterExtracting": "Extracting Update...", + "UpdaterDownloading": "Downloading Update...", + "Game": "Game", + "Docked": "Docked", + "Handheld": "Handheld", + "ConnectionError": "Connection Error.", + "AboutPageDeveloperListMore": "{0} and more...", + "ApiError": "API Error.", + "LoadingHeading": "Loading {0}", + "CompilingPPTC": "Compiling PTC", + "CompilingShaders": "Compiling Shaders", + "AllKeyboards": "All keyboards", + "OpenFileDialogTitle": "Select a supported file to open", + "OpenFolderDialogTitle": "Select a folder with an unpacked game", + "AllSupportedFormats": "All Supported Formats", + "RyujinxUpdater": "Ryujinx Updater", + "SettingsTabHotkeys": "Keyboard Hotkeys", + "SettingsTabHotkeysHotkeys": "Keyboard Hotkeys", + "SettingsTabHotkeysToggleVsyncHotkey": "Toggle VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Screenshot:", + "SettingsTabHotkeysShowUiHotkey": "Show UI:", + "SettingsTabHotkeysPauseHotkey": "Pause:", + "SettingsTabHotkeysToggleMuteHotkey": "Mute:", + "ControllerMotionTitle": "Motion Control Settings", + "ControllerRumbleTitle": "Rumble Settings", + "SettingsSelectThemeFileDialogTitle": "Select Theme File", + "SettingsXamlThemeFile": "Xaml Theme File", + "AvatarWindowTitle": "Manage Accounts - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Unknown", + "Usage": "Usage", + "Writable": "Writable", + "SelectDlcDialogTitle": "Select DLC files", + "SelectUpdateDialogTitle": "Select update files", + "UserProfileWindowTitle": "User Profiles Manager", + "CheatWindowTitle": "Cheats Manager", + "DlcWindowTitle": "Downloadable Content Manager", + "UpdateWindowTitle": "Title Update Manager", + "CheatWindowHeading": "Cheats Available for {0} [{1}]", + "DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})", + "UserProfilesEditProfile": "Edit Selected", + "Cancel": "Cancel", + "Save": "Save", + "Discard": "Discard", + "UserProfilesSetProfileImage": "Set Profile Image", + "UserProfileEmptyNameError": "Name is required", + "UserProfileNoImageError": "Profile image must be set", + "GameUpdateWindowHeading": "{0} Update(s) available for {1} ({2})", + "SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:", + "SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:", + "UserProfilesName": "Name:", + "UserProfilesUserId": "User Id:", + "SettingsTabGraphicsBackend": "Graphics Backend", + "SettingsTabGraphicsBackendTooltip": "Graphics Backend to use", + "SettingsEnableTextureRecompression": "Enable Texture Recompression", + "SettingsEnableTextureRecompressionTooltip": "Compresses certain textures in order to reduce VRAM usage.\n\nRecommended for use with GPUs that have less than 4GiB VRAM.\n\nLeave OFF if unsure.", + "SettingsTabGraphicsPreferredGpu": "Preferred GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Select the graphics card that will be used with the Vulkan graphics backend.\n\nDoes not affect the GPU that OpenGL will use.\n\nSet to the GPU flagged as \"dGPU\" if unsure. If there isn't one, leave untouched.", + "SettingsAppRequiredRestartMessage": "Ryujinx Restart Required", + "SettingsGpuBackendRestartMessage": "Graphics Backend or GPU settings have been modified. This will require a restart to be applied", + "SettingsGpuBackendRestartSubMessage": "Do you want to restart now?", + "RyujinxUpdaterMessage": "Do you want to update Ryujinx to the latest version?", + "SettingsTabHotkeysVolumeUpHotkey": "Increase Volume:", + "SettingsTabHotkeysVolumeDownHotkey": "Decrease Volume:", + "SettingsEnableMacroHLE": "Enable Macro HLE", + "SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Manage Saves", + "DeleteUserSave": "Do you want to delete user save for this game?", + "IrreversibleActionNote": "This action is not reversible.", + "SaveManagerHeading": "Manage Saves for {0}", + "SaveManagerTitle": "Save Manager", + "Name": "Name", + "Size": "Size", + "Search": "Search", + "UserProfilesRecoverLostAccounts": "Recover Lost Accounts", + "Recover": "Recover", + "UserProfilesRecoverHeading": "Saves were found for the following accounts" +} diff --git a/src/Ryujinx.Ava/Assets/Locales/en_US.json b/src/Ryujinx.Ava/Assets/Locales/en_US.json new file mode 100644 index 00000000..3a4bfc65 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/en_US.json @@ -0,0 +1,645 @@ +{ + "Language": "English (US)", + "MenuBarFileOpenApplet": "Open Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Open Mii Editor Applet in Standalone mode", + "SettingsTabInputDirectMouseAccess": "Direct Mouse Access", + "SettingsTabSystemMemoryManagerMode": "Memory Manager Mode:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Host (fast)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (fastest, unsafe)", + "SettingsTabSystemUseHypervisor": "Use Hypervisor", + "MenuBarFile": "_File", + "MenuBarFileOpenFromFile": "_Load Application From File", + "MenuBarFileOpenUnpacked": "Load _Unpacked Game", + "MenuBarFileOpenEmuFolder": "Open Ryujinx Folder", + "MenuBarFileOpenLogsFolder": "Open Logs Folder", + "MenuBarFileExit": "_Exit", + "MenuBarOptions": "Options", + "MenuBarOptionsToggleFullscreen": "Toggle Fullscreen", + "MenuBarOptionsStartGamesInFullscreen": "Start Games in Fullscreen Mode", + "MenuBarOptionsStopEmulation": "Stop Emulation", + "MenuBarOptionsSettings": "_Settings", + "MenuBarOptionsManageUserProfiles": "_Manage User Profiles", + "MenuBarActions": "_Actions", + "MenuBarOptionsSimulateWakeUpMessage": "Simulate Wake-up message", + "MenuBarActionsScanAmiibo": "Scan An Amiibo", + "MenuBarTools": "_Tools", + "MenuBarToolsInstallFirmware": "Install Firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Install a firmware from XCI or ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Install a firmware from a directory", + "MenuBarToolsManageFileTypes": "Manage file types", + "MenuBarToolsInstallFileTypes": "Install file types", + "MenuBarToolsUninstallFileTypes": "Uninstall file types", + "MenuBarHelp": "Help", + "MenuBarHelpCheckForUpdates": "Check for Updates", + "MenuBarHelpAbout": "About", + "MenuSearch": "Search...", + "GameListHeaderFavorite": "Fav", + "GameListHeaderIcon": "Icon", + "GameListHeaderApplication": "Name", + "GameListHeaderDeveloper": "Developer", + "GameListHeaderVersion": "Version", + "GameListHeaderTimePlayed": "Play Time", + "GameListHeaderLastPlayed": "Last Played", + "GameListHeaderFileExtension": "File Ext", + "GameListHeaderFileSize": "File Size", + "GameListHeaderPath": "Path", + "GameListContextMenuOpenUserSaveDirectory": "Open User Save Directory", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Opens the directory which contains Application's User Save", + "GameListContextMenuOpenDeviceSaveDirectory": "Open Device Save Directory", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Opens the directory which contains Application's Device Save", + "GameListContextMenuOpenBcatSaveDirectory": "Open BCAT Save Directory", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Opens the directory which contains Application's BCAT Save", + "GameListContextMenuManageTitleUpdates": "Manage Title Updates", + "GameListContextMenuManageTitleUpdatesToolTip": "Opens the Title Update management window", + "GameListContextMenuManageDlc": "Manage DLC", + "GameListContextMenuManageDlcToolTip": "Opens the DLC management window", + "GameListContextMenuOpenModsDirectory": "Open Mods Directory", + "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", + "GameListContextMenuCacheManagement": "Cache Management", + "GameListContextMenuCacheManagementPurgePptc": "Queue PPTC Rebuild", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Trigger PPTC to rebuild at boot time on the next game launch", + "GameListContextMenuCacheManagementPurgeShaderCache": "Purge Shader Cache", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Deletes Application's shader cache", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Open PPTC Directory", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Opens the directory which contains Application's PPTC cache", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Open Shader Cache Directory", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Opens the directory which contains Application's shader cache", + "GameListContextMenuExtractData": "Extract Data", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extract the ExeFS section from Application's current config (including updates)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extract the RomFS section from Application's current config (including updates)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)", + "StatusBarGamesLoaded": "{0}/{1} Games Loaded", + "StatusBarSystemVersion": "System Version: {0}", + "Settings": "Settings", + "SettingsTabGeneral": "User Interface", + "SettingsTabGeneralGeneral": "General", + "SettingsTabGeneralEnableDiscordRichPresence": "Enable Discord Rich Presence", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Check for Updates on Launch", + "SettingsTabGeneralShowConfirmExitDialog": "Show \"Confirm Exit\" Dialog", + "SettingsTabGeneralHideCursorOnIdle": "Hide Cursor on Idle", + "SettingsTabGeneralGameDirectories": "Game Directories", + "SettingsTabGeneralAdd": "Add", + "SettingsTabGeneralRemove": "Remove", + "SettingsTabSystem": "System", + "SettingsTabSystemCore": "Core", + "SettingsTabSystemSystemRegion": "System Region:", + "SettingsTabSystemSystemRegionJapan": "Japan", + "SettingsTabSystemSystemRegionUSA": "USA", + "SettingsTabSystemSystemRegionEurope": "Europe", + "SettingsTabSystemSystemRegionAustralia": "Australia", + "SettingsTabSystemSystemRegionChina": "China", + "SettingsTabSystemSystemRegionKorea": "Korea", + "SettingsTabSystemSystemRegionTaiwan": "Taiwan", + "SettingsTabSystemSystemLanguage": "System Language:", + "SettingsTabSystemSystemLanguageJapanese": "Japanese", + "SettingsTabSystemSystemLanguageAmericanEnglish": "American English", + "SettingsTabSystemSystemLanguageFrench": "French", + "SettingsTabSystemSystemLanguageGerman": "German", + "SettingsTabSystemSystemLanguageItalian": "Italian", + "SettingsTabSystemSystemLanguageSpanish": "Spanish", + "SettingsTabSystemSystemLanguageChinese": "Chinese", + "SettingsTabSystemSystemLanguageKorean": "Korean", + "SettingsTabSystemSystemLanguageDutch": "Dutch", + "SettingsTabSystemSystemLanguagePortuguese": "Portuguese", + "SettingsTabSystemSystemLanguageRussian": "Russian", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanese", + "SettingsTabSystemSystemLanguageBritishEnglish": "British English", + "SettingsTabSystemSystemLanguageCanadianFrench": "Canadian French", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Latin American Spanish", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Simplified Chinese", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Traditional Chinese", + "SettingsTabSystemSystemTimeZone": "System TimeZone:", + "SettingsTabSystemSystemTime": "System Time:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "FS Integrity Checks", + "SettingsTabSystemAudioBackend": "Audio Backend:", + "SettingsTabSystemAudioBackendDummy": "Dummy", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": "May cause instability", + "SettingsTabSystemExpandDramSize": "Use alternative memory layout (Developers)", + "SettingsTabSystemIgnoreMissingServices": "Ignore Missing Services", + "SettingsTabGraphics": "Graphics", + "SettingsTabGraphicsAPI": "Graphics API", + "SettingsTabGraphicsEnableShaderCache": "Enable Shader Cache", + "SettingsTabGraphicsAnisotropicFiltering": "Anisotropic Filtering:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Resolution Scale:", + "SettingsTabGraphicsResolutionScaleCustom": "Custom (Not recommended)", + "SettingsTabGraphicsResolutionScaleNative": "Native (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Aspect Ratio:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Stretch to Fit Window", + "SettingsTabGraphicsDeveloperOptions": "Developer Options", + "SettingsTabGraphicsShaderDumpPath": "Graphics Shader Dump Path:", + "SettingsTabLogging": "Logging", + "SettingsTabLoggingLogging": "Logging", + "SettingsTabLoggingEnableLoggingToFile": "Enable Logging to File", + "SettingsTabLoggingEnableStubLogs": "Enable Stub Logs", + "SettingsTabLoggingEnableInfoLogs": "Enable Info Logs", + "SettingsTabLoggingEnableWarningLogs": "Enable Warning Logs", + "SettingsTabLoggingEnableErrorLogs": "Enable Error Logs", + "SettingsTabLoggingEnableTraceLogs": "Enable Trace Logs", + "SettingsTabLoggingEnableGuestLogs": "Enable Guest Logs", + "SettingsTabLoggingEnableFsAccessLogs": "Enable Fs Access Logs", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs Global Access Log Mode:", + "SettingsTabLoggingDeveloperOptions": "Developer Options", + "SettingsTabLoggingDeveloperOptionsNote": "WARNING: Will reduce performance", + "SettingsTabLoggingGraphicsBackendLogLevel": "Graphics Backend Log Level:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "None", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Error", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Slowdowns", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "All", + "SettingsTabLoggingEnableDebugLogs": "Enable Debug Logs", + "SettingsTabInput": "Input", + "SettingsTabInputEnableDockedMode": "Docked Mode", + "SettingsTabInputDirectKeyboardAccess": "Direct Keyboard Access", + "SettingsButtonSave": "Save", + "SettingsButtonClose": "Close", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Cancel", + "SettingsButtonApply": "Apply", + "ControllerSettingsPlayer": "Player", + "ControllerSettingsPlayer1": "Player 1", + "ControllerSettingsPlayer2": "Player 2", + "ControllerSettingsPlayer3": "Player 3", + "ControllerSettingsPlayer4": "Player 4", + "ControllerSettingsPlayer5": "Player 5", + "ControllerSettingsPlayer6": "Player 6", + "ControllerSettingsPlayer7": "Player 7", + "ControllerSettingsPlayer8": "Player 8", + "ControllerSettingsHandheld": "Handheld", + "ControllerSettingsInputDevice": "Input Device", + "ControllerSettingsRefresh": "Refresh", + "ControllerSettingsDeviceDisabled": "Disabled", + "ControllerSettingsControllerType": "Controller Type", + "ControllerSettingsControllerTypeHandheld": "Handheld", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon Pair", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Left", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon Right", + "ControllerSettingsProfile": "Profile", + "ControllerSettingsProfileDefault": "Default", + "ControllerSettingsLoad": "Load", + "ControllerSettingsAdd": "Add", + "ControllerSettingsRemove": "Remove", + "ControllerSettingsButtons": "Buttons", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Directional Pad", + "ControllerSettingsDPadUp": "Up", + "ControllerSettingsDPadDown": "Down", + "ControllerSettingsDPadLeft": "Left", + "ControllerSettingsDPadRight": "Right", + "ControllerSettingsLStick": "Left Stick", + "ControllerSettingsLStickButton": "Button", + "ControllerSettingsLStickUp": "Up", + "ControllerSettingsLStickDown": "Down", + "ControllerSettingsLStickLeft": "Left", + "ControllerSettingsLStickRight": "Right", + "ControllerSettingsLStickStick": "Stick", + "ControllerSettingsLStickInvertXAxis": "Invert Stick X", + "ControllerSettingsLStickInvertYAxis": "Invert Stick Y", + "ControllerSettingsLStickDeadzone": "Deadzone:", + "ControllerSettingsRStick": "Right Stick", + "ControllerSettingsRStickButton": "Button", + "ControllerSettingsRStickUp": "Up", + "ControllerSettingsRStickDown": "Down", + "ControllerSettingsRStickLeft": "Left", + "ControllerSettingsRStickRight": "Right", + "ControllerSettingsRStickStick": "Stick", + "ControllerSettingsRStickInvertXAxis": "Invert Stick X", + "ControllerSettingsRStickInvertYAxis": "Invert Stick Y", + "ControllerSettingsRStickDeadzone": "Deadzone:", + "ControllerSettingsTriggersLeft": "Triggers Left", + "ControllerSettingsTriggersRight": "Triggers Right", + "ControllerSettingsTriggersButtonsLeft": "Trigger Buttons Left", + "ControllerSettingsTriggersButtonsRight": "Trigger Buttons Right", + "ControllerSettingsTriggers": "Triggers", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Buttons Left", + "ControllerSettingsExtraButtonsRight": "Buttons Right", + "ControllerSettingsMisc": "Miscellaneous", + "ControllerSettingsTriggerThreshold": "Trigger Threshold:", + "ControllerSettingsMotion": "Motion", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Use CemuHook compatible motion", + "ControllerSettingsMotionControllerSlot": "Controller Slot:", + "ControllerSettingsMotionMirrorInput": "Mirror Input", + "ControllerSettingsMotionRightJoyConSlot": "Right JoyCon Slot:", + "ControllerSettingsMotionServerHost": "Server Host:", + "ControllerSettingsMotionGyroSensitivity": "Gyro Sensitivity:", + "ControllerSettingsMotionGyroDeadzone": "Gyro Deadzone:", + "ControllerSettingsSave": "Save", + "ControllerSettingsClose": "Close", + "UserProfilesSelectedUserProfile": "Selected User Profile:", + "UserProfilesSaveProfileName": "Save Profile Name", + "UserProfilesChangeProfileImage": "Change Profile Image", + "UserProfilesAvailableUserProfiles": "Available User Profiles:", + "UserProfilesAddNewProfile": "Create Profile", + "UserProfilesDelete": "Delete", + "UserProfilesClose": "Close", + "ProfileNameSelectionWatermark": "Choose a nickname", + "ProfileImageSelectionTitle": "Profile Image Selection", + "ProfileImageSelectionHeader": "Choose a profile Image", + "ProfileImageSelectionNote": "You may import a custom profile image, or select an avatar from system firmware", + "ProfileImageSelectionImportImage": "Import Image File", + "ProfileImageSelectionSelectAvatar": "Select Firmware Avatar", + "InputDialogTitle": "Input Dialog", + "InputDialogOk": "OK", + "InputDialogCancel": "Cancel", + "InputDialogAddNewProfileTitle": "Choose the Profile Name", + "InputDialogAddNewProfileHeader": "Please Enter a Profile Name", + "InputDialogAddNewProfileSubtext": "(Max Length: {0})", + "AvatarChoose": "Choose Avatar", + "AvatarSetBackgroundColor": "Set Background Color", + "AvatarClose": "Close", + "ControllerSettingsLoadProfileToolTip": "Load Profile", + "ControllerSettingsAddProfileToolTip": "Add Profile", + "ControllerSettingsRemoveProfileToolTip": "Remove Profile", + "ControllerSettingsSaveProfileToolTip": "Save Profile", + "MenuBarFileToolsTakeScreenshot": "Take Screenshot", + "MenuBarFileToolsHideUi": "Hide UI", + "GameListContextMenuToggleFavorite": "Toggle Favorite", + "GameListContextMenuToggleFavoriteToolTip": "Toggle Favorite status of Game", + "SettingsTabGeneralTheme": "Theme", + "SettingsTabGeneralThemeCustomTheme": "Custom Theme Path", + "SettingsTabGeneralThemeBaseStyle": "Base Style", + "SettingsTabGeneralThemeBaseStyleDark": "Dark", + "SettingsTabGeneralThemeBaseStyleLight": "Light", + "SettingsTabGeneralThemeEnableCustomTheme": "Enable Custom Theme", + "ButtonBrowse": "Browse", + "ControllerSettingsConfigureGeneral": "Configure", + "ControllerSettingsRumble": "Rumble", + "ControllerSettingsRumbleStrongMultiplier": "Strong Rumble Multiplier", + "ControllerSettingsRumbleWeakMultiplier": "Weak Rumble Multiplier", + "DialogMessageSaveNotAvailableMessage": "There is no savedata for {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Would you like to create savedata for this game?", + "DialogConfirmationTitle": "Ryujinx - Confirmation", + "DialogUpdaterTitle": "Ryujinx - Updater", + "DialogErrorTitle": "Ryujinx - Error", + "DialogWarningTitle": "Ryujinx - Warning", + "DialogExitTitle": "Ryujinx - Exit", + "DialogErrorMessage": "Ryujinx has encountered an error", + "DialogExitMessage": "Are you sure you want to close Ryujinx?", + "DialogExitSubMessage": "All unsaved data will be lost!", + "DialogMessageCreateSaveErrorMessage": "There was an error creating the specified savedata: {0}", + "DialogMessageFindSaveErrorMessage": "There was an error finding the specified savedata: {0}", + "FolderDialogExtractTitle": "Choose the folder to extract into", + "DialogNcaExtractionMessage": "Extracting {0} section from {1}...", + "DialogNcaExtractionTitle": "Ryujinx - NCA Section Extractor", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Extraction failure. The main NCA was not present in the selected file.", + "DialogNcaExtractionCheckLogErrorMessage": "Extraction failure. Read the log file for further information.", + "DialogNcaExtractionSuccessMessage": "Extraction completed successfully.", + "DialogUpdaterConvertFailedMessage": "Failed to convert the current Ryujinx version.", + "DialogUpdaterCancelUpdateMessage": "Cancelling Update!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "You are already using the most updated version of Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "An error has occurred when trying to get release information from GitHub Release. This can be caused if a new release is being compiled by GitHub Actions. Try again in a few minutes.", + "DialogUpdaterConvertFailedGithubMessage": "Failed to convert the received Ryujinx version from Github Release.", + "DialogUpdaterDownloadingMessage": "Downloading Update...", + "DialogUpdaterExtractionMessage": "Extracting Update...", + "DialogUpdaterRenamingMessage": "Renaming Update...", + "DialogUpdaterAddingFilesMessage": "Adding New Update...", + "DialogUpdaterCompleteMessage": "Update Complete!", + "DialogUpdaterRestartMessage": "Do you want to restart Ryujinx now?", + "DialogUpdaterArchNotSupportedMessage": "You are not running a supported system architecture!", + "DialogUpdaterArchNotSupportedSubMessage": "(Only x64 systems are supported!)", + "DialogUpdaterNoInternetMessage": "You are not connected to the Internet!", + "DialogUpdaterNoInternetSubMessage": "Please verify that you have a working Internet connection!", + "DialogUpdaterDirtyBuildMessage": "You Cannot update a Dirty build of Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version.", + "DialogRestartRequiredMessage": "Restart Required", + "DialogThemeRestartMessage": "Theme has been saved. A restart is needed to apply the theme.", + "DialogThemeRestartSubMessage": "Do you want to restart", + "DialogFirmwareInstallEmbeddedMessage": "Would you like to install the firmware embedded in this game? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\\nThe emulator will now start.", + "DialogFirmwareNoFirmwareInstalledMessage": "No Firmware Installed", + "DialogFirmwareInstalledMessage": "Firmware {0} was installed", + "DialogInstallFileTypesSuccessMessage": "Successfully installed file types!", + "DialogInstallFileTypesErrorMessage": "Failed to install file types.", + "DialogUninstallFileTypesSuccessMessage": "Successfully uninstalled file types!", + "DialogUninstallFileTypesErrorMessage": "Failed to uninstall file types.", + "DialogOpenSettingsWindowLabel": "Open Settings Window", + "DialogControllerAppletTitle": "Controller Applet", + "DialogMessageDialogErrorExceptionMessage": "Error displaying Message Dialog: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Error displaying Software Keyboard: {0}", + "DialogErrorAppletErrorExceptionMessage": "Error displaying ErrorApplet Dialog: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nFor more information on how to fix this error, follow our Setup Guide.", + "DialogUserErrorDialogTitle": "Ryujinx Error ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "An error occured while fetching information from the API.", + "DialogAmiiboApiConnectErrorMessage": "Unable to connect to Amiibo API server. The service may be down or you may need to verify your internet connection is online.", + "DialogProfileInvalidProfileErrorMessage": "Profile {0} is incompatible with the current input configuration system.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Default Profile can not be overwritten", + "DialogProfileDeleteProfileTitle": "Deleting Profile", + "DialogProfileDeleteProfileMessage": "This action is irreversible, are you sure you want to continue?", + "DialogWarning": "Warning", + "DialogPPTCDeletionMessage": "You are about to queue a PPTC rebuild on the next boot of:\n\n{0}\n\nAre you sure you want to proceed?", + "DialogPPTCDeletionErrorMessage": "Error purging PPTC cache at {0}: {1}", + "DialogShaderDeletionMessage": "You are about to delete the Shader cache for :\n\n{0}\n\nAre you sure you want to proceed?", + "DialogShaderDeletionErrorMessage": "Error purging Shader cache at {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx has encountered an error", + "DialogInvalidTitleIdErrorMessage": "UI error: The selected game did not have a valid title ID", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "A valid system firmware was not found in {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Install Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "System version {0} will be installed.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nThis will replace the current system version {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nDo you want to continue?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Installing firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "System version {0} successfully installed.", + "DialogUserProfileDeletionWarningMessage": "There would be no other profiles to be opened if selected profile is deleted", + "DialogUserProfileDeletionConfirmMessage": "Do you want to delete the selected profile", + "DialogUserProfileUnsavedChangesTitle": "Warning - Unsaved Changes", + "DialogUserProfileUnsavedChangesMessage": "You have made changes to this user profile that have not been saved.", + "DialogUserProfileUnsavedChangesSubMessage": "Do you want to discard your changes?", + "DialogControllerSettingsModifiedConfirmMessage": "The current controller settings has been updated.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Do you want to save?", + "DialogLoadNcaErrorMessage": "{0}. Errored File: {1}", + "DialogDlcNoDlcErrorMessage": "The specified file does not contain a DLC for the selected title!", + "DialogPerformanceCheckLoggingEnabledMessage": "You have trace logging enabled, which is designed to be used by developers only.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "For optimal performance, it's recommended to disable trace logging. Would you like to disable trace logging now?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "You have shader dumping enabled, which is designed to be used by developers only.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "For optimal performance, it's recommended to disable shader dumping. Would you like to disable shader dumping now?", + "DialogLoadAppGameAlreadyLoadedMessage": "A game has already been loaded", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Please stop emulation or close the emulator before launching another game.", + "DialogUpdateAddUpdateErrorMessage": "The specified file does not contain an update for the selected title!", + "DialogSettingsBackendThreadingWarningTitle": "Warning - Backend Threading", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx must be restarted after changing this option for it to apply fully. Depending on your platform, you may need to manually disable your driver's own multithreading when using Ryujinx's.", + "SettingsTabGraphicsFeaturesOptions": "Features", + "SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading:", + "CommonAuto": "Auto", + "CommonOff": "Off", + "CommonOn": "On", + "InputDialogYes": "Yes", + "InputDialogNo": "No", + "DialogProfileInvalidProfileNameErrorMessage": "The file name contains invalid characters. Please try again.", + "MenuBarOptionsPauseEmulation": "Pause", + "MenuBarOptionsResumeEmulation": "Resume", + "AboutUrlTooltipMessage": "Click to open the Ryujinx website in your default browser.", + "AboutDisclaimerMessage": "Ryujinx is not affiliated with Nintendo™,\nor any of its partners, in any way.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) is used\nin our Amiibo emulation.", + "AboutPatreonUrlTooltipMessage": "Click to open the Ryujinx Patreon page in your default browser.", + "AboutGithubUrlTooltipMessage": "Click to open the Ryujinx GitHub page in your default browser.", + "AboutDiscordUrlTooltipMessage": "Click to open an invite to the Ryujinx Discord server in your default browser.", + "AboutTwitterUrlTooltipMessage": "Click to open the Ryujinx Twitter page in your default browser.", + "AboutRyujinxAboutTitle": "About:", + "AboutRyujinxAboutContent": "Ryujinx is an emulator for the Nintendo Switch™.\nPlease support us on Patreon.\nGet all the latest news on our Twitter or Discord.\nDevelopers interested in contributing can find out more on our GitHub or Discord.", + "AboutRyujinxMaintainersTitle": "Maintained By:", + "AboutRyujinxMaintainersContentTooltipMessage": "Click to open the Contributors page in your default browser.", + "AboutRyujinxSupprtersTitle": "Supported on Patreon By:", + "AmiiboSeriesLabel": "Amiibo Series", + "AmiiboCharacterLabel": "Character", + "AmiiboScanButtonLabel": "Scan It", + "AmiiboOptionsShowAllLabel": "Show All Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack: Use Random tag Uuid", + "DlcManagerTableHeadingEnabledLabel": "Enabled", + "DlcManagerTableHeadingTitleIdLabel": "Title ID", + "DlcManagerTableHeadingContainerPathLabel": "Container Path", + "DlcManagerTableHeadingFullPathLabel": "Full Path", + "DlcManagerRemoveAllButton": "Remove All", + "DlcManagerEnableAllButton": "Enable All", + "DlcManagerDisableAllButton": "Disable All", + "MenuBarOptionsChangeLanguage": "Change Language", + "MenuBarShowFileTypes": "Show File Types", + "CommonSort": "Sort", + "CommonShowNames": "Show Names", + "CommonFavorite": "Favorite", + "OrderAscending": "Ascending", + "OrderDescending": "Descending", + "SettingsTabGraphicsFeatures": "Features & Enhancements", + "ErrorWindowTitle": "Error Window", + "ToggleDiscordTooltip": "Choose whether or not to display Ryujinx on your \"currently playing\" Discord activity", + "AddGameDirBoxTooltip": "Enter a game directory to add to the list", + "AddGameDirTooltip": "Add a game directory to the list", + "RemoveGameDirTooltip": "Remove selected game directory", + "CustomThemeCheckTooltip": "Use a custom Avalonia theme for the GUI to change the appearance of the emulator menus", + "CustomThemePathTooltip": "Path to custom GUI theme", + "CustomThemeBrowseTooltip": "Browse for a custom GUI theme", + "DockModeToggleTooltip": "Docked mode makes the emulated system behave as a docked Nintendo Switch. This improves graphical fidelity in most games. Conversely, disabling this will make the emulated system behave as a handheld Nintendo Switch, reducing graphics quality.\n\nConfigure player 1 controls if planning to use docked mode; configure handheld controls if planning to use handheld mode.\n\nLeave ON if unsure.", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.", + "RegionTooltip": "Change System Region", + "LanguageTooltip": "Change System Language", + "TimezoneTooltip": "Change System TimeZone", + "TimeTooltip": "Change System Time", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference. We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", + "PptcToggleTooltip": "Saves translated JIT functions so that they do not need to be translated every time the game loads.\n\nReduces stuttering and significantly speeds up boot times after the first boot of a game.\n\nLeave ON if unsure.", + "FsIntegrityToggleTooltip": "Checks for corrupt files when booting a game, and if corrupt files are detected, displays a hash error in the log.\n\nHas no impact on performance and is meant to help troubleshooting.\n\nLeave ON if unsure.", + "AudioBackendTooltip": "Changes the backend used to render audio.\n\nSDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound.\n\nSet to SDL2 if unsure.", + "MemoryManagerTooltip": "Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance.\n\nSet to HOST UNCHECKED if unsure.", + "MemoryManagerSoftwareTooltip": "Use a software page table for address translation. Highest accuracy but slowest performance.", + "MemoryManagerHostTooltip": "Directly map memory in the host address space. Much faster JIT compilation and execution.", + "MemoryManagerUnsafeTooltip": "Directly map memory, but do not mask the address within the guest address space before access. Faster, but at the cost of safety. The guest application can access memory from anywhere in Ryujinx, so only run programs you trust with this mode.", + "UseHypervisorTooltip": "Use Hypervisor instead of JIT. Greatly improves performance when available, but can be unstable in its current state.", + "DRamTooltip": "Utilizes an alternative MemoryMode layout to mimic a Switch development model.\n\nThis is only useful for higher-resolution texture packs or 4k resolution mods. Does NOT improve performance.\n\nLeave OFF if unsure.", + "IgnoreMissingServicesTooltip": "Ignores unimplemented Horizon OS services. This may help in bypassing crashes when booting certain games.\n\nLeave OFF if unsure.", + "GraphicsBackendThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", + "GalThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", + "ShaderCacheToggleTooltip": "Saves a disk shader cache which reduces stuttering in subsequent runs.\n\nLeave ON if unsure.", + "ResolutionScaleTooltip": "Resolution Scale applied to applicable render targets", + "ResolutionScaleEntryTooltip": "Floating point resolution scale, such as 1.5. Non-integral scales are more likely to cause issues or crash.", + "AnisotropyTooltip": "Level of Anisotropic Filtering (set to Auto to use the value requested by the game)", + "AspectRatioTooltip": "Aspect Ratio applied to the renderer window.", + "ShaderDumpPathTooltip": "Graphics Shaders Dump Path", + "FileLogTooltip": "Saves console logging to a log file on disk. Does not affect performance.", + "StubLogTooltip": "Prints stub log messages in the console. Does not affect performance.", + "InfoLogTooltip": "Prints info log messages in the console. Does not affect performance.", + "WarnLogTooltip": "Prints warning log messages in the console. Does not affect performance.", + "ErrorLogTooltip": "Prints error log messages in the console. Does not affect performance.", + "TraceLogTooltip": "Prints trace log messages in the console. Does not affect performance.", + "GuestLogTooltip": "Prints guest log messages in the console. Does not affect performance.", + "FileAccessLogTooltip": "Prints file access log messages in the console.", + "FSAccessLogModeTooltip": "Enables FS access log output to the console. Possible modes are 0-3", + "DeveloperOptionTooltip": "Use with care", + "OpenGlLogLevel": "Requires appropriate log levels enabled", + "DebugLogTooltip": "Prints debug log messages in the console.\n\nOnly use this if specifically instructed by a staff member, as it will make logs difficult to read and worsen emulator performance.", + "LoadApplicationFileTooltip": "Open a file explorer to choose a Switch compatible file to load", + "LoadApplicationFolderTooltip": "Open a file explorer to choose a Switch compatible, unpacked application to load", + "OpenRyujinxFolderTooltip": "Open Ryujinx filesystem folder", + "OpenRyujinxLogsTooltip": "Opens the folder where logs are written to", + "ExitTooltip": "Exit Ryujinx", + "OpenSettingsTooltip": "Open settings window", + "OpenProfileManagerTooltip": "Open User Profiles Manager window", + "StopEmulationTooltip": "Stop emulation of the current game and return to game selection", + "CheckUpdatesTooltip": "Check for updates to Ryujinx", + "OpenAboutTooltip": "Open About Window", + "GridSize": "Grid Size", + "GridSizeTooltip": "Change the size of grid items", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brazilian Portuguese", + "AboutRyujinxContributorsButtonHeader": "See All Contributors", + "SettingsTabSystemAudioVolume": "Volume: ", + "AudioVolumeTooltip": "Change Audio Volume", + "SettingsTabSystemEnableInternetAccess": "Guest Internet Access/LAN Mode", + "EnableInternetAccessTooltip": "Allows the emulated application to connect to the Internet.\n\nGames with a LAN mode can connect to each other when this is enabled and the systems are connected to the same access point. This includes real consoles as well.\n\nDoes NOT allow connecting to Nintendo servers. May cause crashing in certain games that try to connect to the Internet.\n\nLeave OFF if unsure.", + "GameListContextMenuManageCheatToolTip": "Manage Cheats", + "GameListContextMenuManageCheat": "Manage Cheats", + "ControllerSettingsStickRange": "Range:", + "DialogStopEmulationTitle": "Ryujinx - Stop Emulation", + "DialogStopEmulationMessage": "Are you sure you want to stop emulation?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Audio", + "SettingsTabNetwork": "Network", + "SettingsTabNetworkConnection": "Network Connection", + "SettingsTabCpuCache": "CPU Cache", + "SettingsTabCpuMemory": "CPU Mode", + "DialogUpdaterFlatpakNotSupportedMessage": "Please update Ryujinx via FlatHub.", + "UpdaterDisabledWarningTitle": "Updater Disabled!", + "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", + "ControllerSettingsRotate90": "Rotate 90° Clockwise", + "IconSize": "Icon Size", + "IconSizeTooltip": "Change the size of game icons", + "MenuBarOptionsShowConsole": "Show Console", + "ShaderCachePurgeError": "Error purging shader cache at {0}: {1}", + "UserErrorNoKeys": "Keys not found", + "UserErrorNoFirmware": "Firmware not found", + "UserErrorFirmwareParsingFailed": "Firmware parsing error", + "UserErrorApplicationNotFound": "Application not found", + "UserErrorUnknown": "Unknown error", + "UserErrorUndefined": "Undefined error", + "UserErrorNoKeysDescription": "Ryujinx was unable to find your 'prod.keys' file", + "UserErrorNoFirmwareDescription": "Ryujinx was unable to find any firmwares installed", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx was unable to parse the provided firmware. This is usually caused by outdated keys.", + "UserErrorApplicationNotFoundDescription": "Ryujinx couldn't find a valid application at the given path.", + "UserErrorUnknownDescription": "An unknown error occured!", + "UserErrorUndefinedDescription": "An undefined error occured! This shouldn't happen, please contact a dev!", + "OpenSetupGuideMessage": "Open the Setup Guide", + "NoUpdate": "No Update", + "TitleUpdateVersionLabel": "Version {0}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Confirmation", + "FileDialogAllTypes": "All types", + "Never": "Never", + "SwkbdMinCharacters": "Must be at least {0} characters long", + "SwkbdMinRangeCharacters": "Must be {0}-{1} characters long", + "SoftwareKeyboard": "Software Keyboard", + "DialogControllerAppletMessagePlayerRange": "Application requests {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.", + "DialogControllerAppletMessage": "Application requests exactly {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.", + "DialogControllerAppletDockModeSet": "Docked mode set. Handheld is also invalid.\n\n", + "UpdaterRenaming": "Renaming Old Files...", + "UpdaterRenameFailed": "Updater was unable to rename file: {0}", + "UpdaterAddingFiles": "Adding New Files...", + "UpdaterExtracting": "Extracting Update...", + "UpdaterDownloading": "Downloading Update...", + "Game": "Game", + "Docked": "Docked", + "Handheld": "Handheld", + "ConnectionError": "Connection Error.", + "AboutPageDeveloperListMore": "{0} and more...", + "ApiError": "API Error.", + "LoadingHeading": "Loading {0}", + "CompilingPPTC": "Compiling PTC", + "CompilingShaders": "Compiling Shaders", + "AllKeyboards": "All keyboards", + "OpenFileDialogTitle": "Select a supported file to open", + "OpenFolderDialogTitle": "Select a folder with an unpacked game", + "AllSupportedFormats": "All Supported Formats", + "RyujinxUpdater": "Ryujinx Updater", + "SettingsTabHotkeys": "Keyboard Hotkeys", + "SettingsTabHotkeysHotkeys": "Keyboard Hotkeys", + "SettingsTabHotkeysToggleVsyncHotkey": "Toggle VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Screenshot:", + "SettingsTabHotkeysShowUiHotkey": "Show UI:", + "SettingsTabHotkeysPauseHotkey": "Pause:", + "SettingsTabHotkeysToggleMuteHotkey": "Mute:", + "ControllerMotionTitle": "Motion Control Settings", + "ControllerRumbleTitle": "Rumble Settings", + "SettingsSelectThemeFileDialogTitle": "Select Theme File", + "SettingsXamlThemeFile": "Xaml Theme File", + "AvatarWindowTitle": "Manage Accounts - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Unknown", + "Usage": "Usage", + "Writable": "Writable", + "SelectDlcDialogTitle": "Select DLC files", + "SelectUpdateDialogTitle": "Select update files", + "UserProfileWindowTitle": "User Profiles Manager", + "CheatWindowTitle": "Cheats Manager", + "DlcWindowTitle": "Manage Downloadable Content for {0} ({1})", + "UpdateWindowTitle": "Title Update Manager", + "CheatWindowHeading": "Cheats Available for {0} [{1}]", + "DlcWindowHeading": "{0} Downloadable Content(s)", + "UserProfilesEditProfile": "Edit Selected", + "Cancel": "Cancel", + "Save": "Save", + "Discard": "Discard", + "UserProfilesSetProfileImage": "Set Profile Image", + "UserProfileEmptyNameError": "Name is required", + "UserProfileNoImageError": "Profile image must be set", + "GameUpdateWindowHeading": "Manage Updates for {0} ({1})", + "SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:", + "SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:", + "UserProfilesName": "Name:", + "UserProfilesUserId": "User ID:", + "SettingsTabGraphicsBackend": "Graphics Backend", + "SettingsTabGraphicsBackendTooltip": "Graphics Backend to use", + "SettingsEnableTextureRecompression": "Enable Texture Recompression", + "SettingsEnableTextureRecompressionTooltip": "Compresses certain textures in order to reduce VRAM usage.\n\nRecommended for use with GPUs that have less than 4GiB VRAM.\n\nLeave OFF if unsure.", + "SettingsTabGraphicsPreferredGpu": "Preferred GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Select the graphics card that will be used with the Vulkan graphics backend.\n\nDoes not affect the GPU that OpenGL will use.\n\nSet to the GPU flagged as \"dGPU\" if unsure. If there isn't one, leave untouched.", + "SettingsAppRequiredRestartMessage": "Ryujinx Restart Required", + "SettingsGpuBackendRestartMessage": "Graphics Backend or GPU settings have been modified. This will require a restart to be applied", + "SettingsGpuBackendRestartSubMessage": "Do you want to restart now?", + "RyujinxUpdaterMessage": "Do you want to update Ryujinx to the latest version?", + "SettingsTabHotkeysVolumeUpHotkey": "Increase Volume:", + "SettingsTabHotkeysVolumeDownHotkey": "Decrease Volume:", + "SettingsEnableMacroHLE": "Enable Macro HLE", + "SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Manage Saves", + "DeleteUserSave": "Do you want to delete user save for this game?", + "IrreversibleActionNote": "This action is not reversible.", + "SaveManagerHeading": "Manage Saves for {0} ({1})", + "SaveManagerTitle": "Save Manager", + "Name": "Name", + "Size": "Size", + "Search": "Search", + "UserProfilesRecoverLostAccounts": "Recover Lost Accounts", + "Recover": "Recover", + "UserProfilesRecoverHeading" : "Saves were found for the following accounts", + "UserProfilesRecoverEmptyList": "No profiles to recover", + "GraphicsAATooltip": "Applies anti-aliasing to the game render", + "GraphicsAALabel": "Anti-Aliasing:", + "GraphicsScalingFilterLabel": "Scaling Filter:", + "GraphicsScalingFilterTooltip": "Enables Framebuffer Scaling", + "GraphicsScalingFilterLevelLabel": "Level", + "GraphicsScalingFilterLevelTooltip": "Set Scaling Filter Level", + "SmaaLow": "SMAA Low", + "SmaaMedium": "SMAA Medium", + "SmaaHigh": "SMAA High", + "SmaaUltra": "SMAA Ultra", + "UserEditorTitle" : "Edit User", + "UserEditorTitleCreate" : "Create User", + "SettingsTabNetworkInterface": "Network Interface:", + "NetworkInterfaceTooltip": "The network interface used for LAN features", + "NetworkInterfaceDefault": "Default" +} diff --git a/src/Ryujinx.Ava/Assets/Locales/es_ES.json b/src/Ryujinx.Ava/Assets/Locales/es_ES.json new file mode 100644 index 00000000..13b05fc0 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/es_ES.json @@ -0,0 +1,614 @@ +{ + "Language": "Español (ES)", + "MenuBarFileOpenApplet": "Abrir applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Abre el editor de Mii en modo autónomo", + "SettingsTabInputDirectMouseAccess": "Acceso directo al ratón", + "SettingsTabSystemMemoryManagerMode": "Modo del administrador de memoria:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Host (rápido)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host sin verificación (más rápido, inseguro)", + "MenuBarFile": "_Archivo", + "MenuBarFileOpenFromFile": "_Cargar aplicación desde un archivo", + "MenuBarFileOpenUnpacked": "Cargar juego _desempaquetado", + "MenuBarFileOpenEmuFolder": "Abrir carpeta de Ryujinx", + "MenuBarFileOpenLogsFolder": "Abrir carpeta de registros", + "MenuBarFileExit": "_Salir", + "MenuBarOptions": "_Opciones", + "MenuBarOptionsToggleFullscreen": "Cambiar a pantalla completa.", + "MenuBarOptionsStartGamesInFullscreen": "Iniciar juegos en pantalla completa", + "MenuBarOptionsStopEmulation": "Detener emulación", + "MenuBarOptionsSettings": "_Configuración", + "MenuBarOptionsManageUserProfiles": "_Gestionar perfiles de usuario", + "MenuBarActions": "_Acciones", + "MenuBarOptionsSimulateWakeUpMessage": "Simular mensaje de reactivación", + "MenuBarActionsScanAmiibo": "Escanear Amiibo", + "MenuBarTools": "_Herramientas", + "MenuBarToolsInstallFirmware": "Instalar firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Instalar firmware desde un archivo XCI o ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Instalar firmware desde una carpeta", + "MenuBarHelp": "Ayuda", + "MenuBarHelpCheckForUpdates": "Buscar actualizaciones", + "MenuBarHelpAbout": "Acerca de", + "MenuSearch": "Buscar...", + "GameListHeaderFavorite": "Favoritos", + "GameListHeaderIcon": "Icono", + "GameListHeaderApplication": "Nombre", + "GameListHeaderDeveloper": "Desarrollador", + "GameListHeaderVersion": "Versión", + "GameListHeaderTimePlayed": "Tiempo jugado", + "GameListHeaderLastPlayed": "Jugado por última vez", + "GameListHeaderFileExtension": "Extensión", + "GameListHeaderFileSize": "Tamaño del archivo", + "GameListHeaderPath": "Directorio", + "GameListContextMenuOpenUserSaveDirectory": "Abrir carpeta de guardado de este usuario", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Abre la carpeta que contiene la partida guardada del usuario para esta aplicación", + "GameListContextMenuOpenDeviceSaveDirectory": "Abrir carpeta de guardado del sistema para el usuario actual", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Abre la carpeta que contiene la partida guardada del sistema para esta aplicación", + "GameListContextMenuOpenBcatSaveDirectory": "Abrir carpeta de guardado BCAT del usuario", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Abrir la carpeta que contiene el guardado BCAT de esta aplicación", + "GameListContextMenuManageTitleUpdates": "Gestionar actualizaciones del juego", + "GameListContextMenuManageTitleUpdatesToolTip": "Abrir la ventana de gestión de actualizaciones de esta aplicación", + "GameListContextMenuManageDlc": "Gestionar DLC", + "GameListContextMenuManageDlcToolTip": "Abrir la ventana de gestión del DLC", + "GameListContextMenuOpenModsDirectory": "Abrir carpeta de mods", + "GameListContextMenuOpenModsDirectoryToolTip": "Abrir la carpeta que contiene los mods (archivos modificantes) de esta aplicación", + "GameListContextMenuCacheManagement": "Gestión de caché ", + "GameListContextMenuCacheManagementPurgePptc": "Reconstruir PPTC en cola", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Elimina la caché de PPTC de esta aplicación", + "GameListContextMenuCacheManagementPurgeShaderCache": "Limpiar caché de sombras", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Eliminar la caché de sombras de esta aplicación", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Abrir carpeta de PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Abrir la carpeta que contiene la caché de PPTC de esta aplicación", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Abrir carpeta de caché de sombras", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Abrir la carpeta que contiene la caché de sombras de esta aplicación", + "GameListContextMenuExtractData": "Extraer datos", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extraer la sección ExeFS de la configuración actual de la aplicación (incluyendo actualizaciones)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extraer la sección RomFS de la configuración actual de la aplicación (incluyendo actualizaciones)", + "GameListContextMenuExtractDataLogo": "Logotipo", + "GameListContextMenuExtractDataLogoToolTip": "Extraer la sección Logo de la configuración actual de la aplicación (incluyendo actualizaciones)", + "StatusBarGamesLoaded": "{0}/{1} juegos cargados", + "StatusBarSystemVersion": "Versión del sistema: {0}", + "Settings": "Configuración", + "SettingsTabGeneral": "Interfaz de usuario", + "SettingsTabGeneralGeneral": "General", + "SettingsTabGeneralEnableDiscordRichPresence": "Habilitar estado en Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Buscar actualizaciones al iniciar", + "SettingsTabGeneralShowConfirmExitDialog": "Mostrar diálogo de confirmación al cerrar", + "SettingsTabGeneralHideCursorOnIdle": "Ocultar cursor cuando esté inactivo", + "SettingsTabGeneralGameDirectories": "Carpetas de juegos", + "SettingsTabGeneralAdd": "Agregar", + "SettingsTabGeneralRemove": "Quitar", + "SettingsTabSystem": "Sistema", + "SettingsTabSystemCore": "Núcleo", + "SettingsTabSystemSystemRegion": "Región del sistema:", + "SettingsTabSystemSystemRegionJapan": "Japón", + "SettingsTabSystemSystemRegionUSA": "EEUU", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Australia", + "SettingsTabSystemSystemRegionChina": "China", + "SettingsTabSystemSystemRegionKorea": "Corea", + "SettingsTabSystemSystemRegionTaiwan": "Taiwán", + "SettingsTabSystemSystemLanguage": "Idioma del sistema:", + "SettingsTabSystemSystemLanguageJapanese": "Japonés", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Inglés americano", + "SettingsTabSystemSystemLanguageFrench": "Francés", + "SettingsTabSystemSystemLanguageGerman": "Alemán", + "SettingsTabSystemSystemLanguageItalian": "Italiano", + "SettingsTabSystemSystemLanguageSpanish": "Español", + "SettingsTabSystemSystemLanguageChinese": "Chino", + "SettingsTabSystemSystemLanguageKorean": "Coreano", + "SettingsTabSystemSystemLanguageDutch": "Neerlandés/Holandés", + "SettingsTabSystemSystemLanguagePortuguese": "Portugués", + "SettingsTabSystemSystemLanguageRussian": "Ruso", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanés", + "SettingsTabSystemSystemLanguageBritishEnglish": "Inglés británico", + "SettingsTabSystemSystemLanguageCanadianFrench": "Francés canadiense", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Español latinoamericano", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chino simplificado", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Chino tradicional", + "SettingsTabSystemSystemTimeZone": "Zona horaria del sistema:", + "SettingsTabSystemSystemTime": "Hora del sistema:", + "SettingsTabSystemEnableVsync": "Sincronización vertical", + "SettingsTabSystemEnablePptc": "PPTC (Cache de Traducción de Perfil Persistente)", + "SettingsTabSystemEnableFsIntegrityChecks": "Comprobar integridad de los archivos", + "SettingsTabSystemAudioBackend": "Motor de audio:", + "SettingsTabSystemAudioBackendDummy": "Vacio", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": " (Pueden causar inestabilidad)", + "SettingsTabSystemExpandDramSize": "Usar diseño alternativo de memoria (Desarrolladores)", + "SettingsTabSystemIgnoreMissingServices": "Ignorar servicios no implementados", + "SettingsTabGraphics": "Gráficos", + "SettingsTabGraphicsAPI": "API de gráficos", + "SettingsTabGraphicsEnableShaderCache": "Habilitar caché de sombras", + "SettingsTabGraphicsAnisotropicFiltering": "Filtro anisotrópico:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Automático", + "SettingsTabGraphicsAnisotropicFiltering2x": "x2", + "SettingsTabGraphicsAnisotropicFiltering4x": "x4", + "SettingsTabGraphicsAnisotropicFiltering8x": "x8", + "SettingsTabGraphicsAnisotropicFiltering16x": "x16", + "SettingsTabGraphicsResolutionScale": "Escala de resolución:", + "SettingsTabGraphicsResolutionScaleCustom": "Personalizada (no recomendado)", + "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "x2 (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "x3 (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "x4 (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Relación de aspecto:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Estirar a la ventana", + "SettingsTabGraphicsDeveloperOptions": "Opciones de desarrollador", + "SettingsTabGraphicsShaderDumpPath": "Directorio de volcado de sombras:", + "SettingsTabLogging": "Registros", + "SettingsTabLoggingLogging": "Registros", + "SettingsTabLoggingEnableLoggingToFile": "Habilitar registro a archivo", + "SettingsTabLoggingEnableStubLogs": "Habilitar registros de Stub", + "SettingsTabLoggingEnableInfoLogs": "Habilitar registros de Info", + "SettingsTabLoggingEnableWarningLogs": "Habilitar registros de Advertencia", + "SettingsTabLoggingEnableErrorLogs": "Habilitar registros de Error", + "SettingsTabLoggingEnableTraceLogs": "Habilitar registros de Rastro", + "SettingsTabLoggingEnableGuestLogs": "Habilitar registros de Guest", + "SettingsTabLoggingEnableFsAccessLogs": "Habilitar registros de Fs Access", + "SettingsTabLoggingFsGlobalAccessLogMode": "Modo de registros Fs Global Access:", + "SettingsTabLoggingDeveloperOptions": "Opciones de desarrollador (ADVERTENCIA: empeorarán el rendimiento)", + "SettingsTabLoggingGraphicsBackendLogLevel": "Nivel de registro de backend gráficos:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Nada", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Errores", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Ralentizaciones", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Todo", + "SettingsTabLoggingEnableDebugLogs": "Habilitar registros de debug", + "SettingsTabInput": "Entrada", + "SettingsTabInputEnableDockedMode": "Modo dock/TV", + "SettingsTabInputDirectKeyboardAccess": "Acceso directo al teclado", + "SettingsButtonSave": "Guardar", + "SettingsButtonClose": "Cerrar", + "SettingsButtonOk": "Aceptar", + "SettingsButtonCancel": "Cancelar", + "SettingsButtonApply": "Aplicar", + "ControllerSettingsPlayer": "Jugador", + "ControllerSettingsPlayer1": "Jugador 1", + "ControllerSettingsPlayer2": "Jugador 2", + "ControllerSettingsPlayer3": "Jugador 3", + "ControllerSettingsPlayer4": "Jugador 4", + "ControllerSettingsPlayer5": "Jugador 5", + "ControllerSettingsPlayer6": "Jugador 6", + "ControllerSettingsPlayer7": "Jugador 7", + "ControllerSettingsPlayer8": "Jugador 8", + "ControllerSettingsHandheld": "Portátil", + "ControllerSettingsInputDevice": "Dispositivo de entrada", + "ControllerSettingsRefresh": "Actualizar", + "ControllerSettingsDeviceDisabled": "Deshabilitado", + "ControllerSettingsControllerType": "Tipo de Mando", + "ControllerSettingsControllerTypeHandheld": "Portátil", + "ControllerSettingsControllerTypeProController": "Mando Pro", + "ControllerSettingsControllerTypeJoyConPair": "Doble Joy-Con", + "ControllerSettingsControllerTypeJoyConLeft": "Joy-Con Izquierdo", + "ControllerSettingsControllerTypeJoyConRight": "Joy-Con Derecho", + "ControllerSettingsProfile": "Perfil", + "ControllerSettingsProfileDefault": "Predeterminado", + "ControllerSettingsLoad": "Cargar", + "ControllerSettingsAdd": "Agregar", + "ControllerSettingsRemove": "Quitar", + "ControllerSettingsButtons": "Botones", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Pad direccional", + "ControllerSettingsDPadUp": "Arriba", + "ControllerSettingsDPadDown": "Abajo", + "ControllerSettingsDPadLeft": "Izquierda", + "ControllerSettingsDPadRight": "Derecha", + "ControllerSettingsLStick": "Palanca izquierda", + "ControllerSettingsLStickButton": "Botón (L3)", + "ControllerSettingsLStickUp": "Arriba", + "ControllerSettingsLStickDown": "Abajo", + "ControllerSettingsLStickLeft": "Izquierda", + "ControllerSettingsLStickRight": "Derecha", + "ControllerSettingsLStickStick": "Palanca", + "ControllerSettingsLStickInvertXAxis": "Invertir eje X", + "ControllerSettingsLStickInvertYAxis": "Invertir eje Y", + "ControllerSettingsLStickDeadzone": "Zona muerta:", + "ControllerSettingsRStick": "Palanca derecha", + "ControllerSettingsRStickButton": "Botón (R3)", + "ControllerSettingsRStickUp": "Arriba", + "ControllerSettingsRStickDown": "Abajo", + "ControllerSettingsRStickLeft": "Izquierda", + "ControllerSettingsRStickRight": "Derecha", + "ControllerSettingsRStickStick": "Palanca", + "ControllerSettingsRStickInvertXAxis": "Invertir eje X", + "ControllerSettingsRStickInvertYAxis": "Invertir eje Y", + "ControllerSettingsRStickDeadzone": "Zona muerta:", + "ControllerSettingsTriggersLeft": "Gatillos izquierdos", + "ControllerSettingsTriggersRight": "Gatillos derechos", + "ControllerSettingsTriggersButtonsLeft": "Botones de gatillo izquierdos", + "ControllerSettingsTriggersButtonsRight": "Botones de gatillo derechos", + "ControllerSettingsTriggers": "Gatillos", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Botones izquierdos", + "ControllerSettingsExtraButtonsRight": "Botones derechos", + "ControllerSettingsMisc": "Misceláneo", + "ControllerSettingsTriggerThreshold": "Límite de gatillos:", + "ControllerSettingsMotion": "Movimiento", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usar movimiento compatible con CemuHook", + "ControllerSettingsMotionControllerSlot": "Puerto del mando:", + "ControllerSettingsMotionMirrorInput": "Paralelizar derecho e izquierdo", + "ControllerSettingsMotionRightJoyConSlot": "Puerto del Joy-Con derecho:", + "ControllerSettingsMotionServerHost": "Host del servidor:", + "ControllerSettingsMotionGyroSensitivity": "Sensibilidad de Gyro:", + "ControllerSettingsMotionGyroDeadzone": "Zona muerta de Gyro:", + "ControllerSettingsSave": "Guardar", + "ControllerSettingsClose": "Cerrar", + "UserProfilesSelectedUserProfile": "Perfil de usuario seleccionado:", + "UserProfilesSaveProfileName": "Guardar nombre de perfil", + "UserProfilesChangeProfileImage": "Cambiar imagen de perfil", + "UserProfilesAvailableUserProfiles": "Perfiles de usuario disponibles:", + "UserProfilesAddNewProfile": "Añadir nuevo perfil", + "UserProfilesDelete": "Eliminar", + "UserProfilesClose": "Cerrar", + "ProfileImageSelectionTitle": "Selección de imagen de perfil", + "ProfileImageSelectionHeader": "Elige una imagen de perfil", + "ProfileImageSelectionNote": "Puedes importar una imagen de perfil personalizada, o seleccionar un avatar del firmware de sistema", + "ProfileImageSelectionImportImage": "Importar imagen", + "ProfileImageSelectionSelectAvatar": "Seleccionar avatar del firmware", + "InputDialogTitle": "Cuadro de diálogo de entrada", + "InputDialogOk": "Aceptar", + "InputDialogCancel": "Cancelar", + "InputDialogAddNewProfileTitle": "Introducir nombre de perfil", + "InputDialogAddNewProfileHeader": "Por favor elige un nombre de usuario", + "InputDialogAddNewProfileSubtext": "(Máximo de caracteres: {0})", + "AvatarChoose": "Escoger", + "AvatarSetBackgroundColor": "Establecer color de fondo", + "AvatarClose": "Cerrar", + "ControllerSettingsLoadProfileToolTip": "Cargar perfil", + "ControllerSettingsAddProfileToolTip": "Agregar perfil", + "ControllerSettingsRemoveProfileToolTip": "Eliminar perfil", + "ControllerSettingsSaveProfileToolTip": "Guardar perfil", + "MenuBarFileToolsTakeScreenshot": "Captura de pantalla", + "MenuBarFileToolsHideUi": "Ocultar interfaz", + "GameListContextMenuToggleFavorite": "Marcar favorito", + "GameListContextMenuToggleFavoriteToolTip": "Marca o desmarca el juego como favorito", + "SettingsTabGeneralTheme": "Tema", + "SettingsTabGeneralThemeCustomTheme": "Directorio de tema personalizado", + "SettingsTabGeneralThemeBaseStyle": "Estilo base", + "SettingsTabGeneralThemeBaseStyleDark": "Oscuro", + "SettingsTabGeneralThemeBaseStyleLight": "Claro", + "SettingsTabGeneralThemeEnableCustomTheme": "Habilitar tema personalizado", + "ButtonBrowse": "Buscar", + "ControllerSettingsConfigureGeneral": "Configurar", + "ControllerSettingsRumble": "Vibración", + "ControllerSettingsRumbleStrongMultiplier": "Multiplicador de vibraciones fuertes", + "ControllerSettingsRumbleWeakMultiplier": "Multiplicador de vibraciones débiles", + "DialogMessageSaveNotAvailableMessage": "No hay datos de guardado para {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "¿Quieres crear datos de guardado para este juego?", + "DialogConfirmationTitle": "Ryujinx - Confirmación", + "DialogUpdaterTitle": "Ryujinx - Actualizador", + "DialogErrorTitle": "Ryujinx - Error", + "DialogWarningTitle": "Ryujinx - Advertencia", + "DialogExitTitle": "Ryujinx - Salir", + "DialogErrorMessage": "Ryujinx encontró un error", + "DialogExitMessage": "¿Seguro que quieres cerrar Ryujinx?", + "DialogExitSubMessage": "¡Se perderán los datos no guardados!", + "DialogMessageCreateSaveErrorMessage": "Hubo un error al crear los datos de guardado especificados: {0}", + "DialogMessageFindSaveErrorMessage": "Hubo un error encontrando los datos de guardado especificados: {0}", + "FolderDialogExtractTitle": "Elige la carpeta en la que deseas extraer", + "DialogNcaExtractionMessage": "Extrayendo {0} sección de {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Extractor de sección NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Fallo de extracción. El NCA principal no estaba presente en el archivo seleccionado.", + "DialogNcaExtractionCheckLogErrorMessage": "Fallo de extracción. Lee el registro para más información.", + "DialogNcaExtractionSuccessMessage": "Se completó la extracción con éxito.", + "DialogUpdaterConvertFailedMessage": "No se pudo convertir la versión actual de Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "¡Cancelando actualización!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "¡Ya tienes la versión más reciente de Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Se ha producido un error al intentar obtener información de liberación de GitHub Release. Esto puede ser causado si una nueva versión está siendo compilada por GitHub Actions. Inténtalo de nuevo en unos minutos.", + "DialogUpdaterConvertFailedGithubMessage": "No se pudo convertir la versión de Ryujinx recibida de GitHub Release.", + "DialogUpdaterDownloadingMessage": "Descargando actualización...", + "DialogUpdaterExtractionMessage": "Extrayendo actualización...", + "DialogUpdaterRenamingMessage": "Renombrando actualización...", + "DialogUpdaterAddingFilesMessage": "Aplicando actualización...", + "DialogUpdaterCompleteMessage": "¡Actualización completa!", + "DialogUpdaterRestartMessage": "¿Quieres reiniciar Ryujinx?", + "DialogUpdaterArchNotSupportedMessage": "¡Tu arquitectura de sistema no es compatible!", + "DialogUpdaterArchNotSupportedSubMessage": "(¡Solo son compatibles los sistemas x64!)", + "DialogUpdaterNoInternetMessage": "¡No estás conectado a internet!", + "DialogUpdaterNoInternetSubMessage": "¡Por favor, verifica que tu conexión a Internet funciona!", + "DialogUpdaterDirtyBuildMessage": "¡No puedes actualizar una versión \"dirty\" de Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Por favor, descarga Ryujinx en https://ryujinx.org/ si buscas una versión con soporte.", + "DialogRestartRequiredMessage": "Se necesita reiniciar", + "DialogThemeRestartMessage": "Tema guardado. Se necesita reiniciar para aplicar el tema.", + "DialogThemeRestartSubMessage": "¿Quieres reiniciar?", + "DialogFirmwareInstallEmbeddedMessage": "¿Quieres instalar el firmware incluido en este juego? (Firmware versión {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "No se encontró firmware instalado pero Ryujinx pudo instalar el firmware {0} a partir de este juego.\nA continuación, se iniciará el emulador.", + "DialogFirmwareNoFirmwareInstalledMessage": "No hay firmware instalado", + "DialogFirmwareInstalledMessage": "Se instaló el firmware {0}", + "DialogOpenSettingsWindowLabel": "Abrir ventana de opciones", + "DialogControllerAppletTitle": "Applet de mandos", + "DialogMessageDialogErrorExceptionMessage": "Error al mostrar cuadro de diálogo: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Error al mostrar teclado de software: {0}", + "DialogErrorAppletErrorExceptionMessage": "Error al mostrar díalogo ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nPara más información sobre cómo arreglar este error, sigue nuestra Guía de Instalación.", + "DialogUserErrorDialogTitle": "Ryujinx Error ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Ocurrió un error al recibir información de la API.", + "DialogAmiiboApiConnectErrorMessage": "No se pudo conectar al servidor de la API Amiibo. El servicio puede estar caído o tu conexión a internet puede haberse desconectado.", + "DialogProfileInvalidProfileErrorMessage": "El perfil {0} no es compatible con el sistema actual de configuración de entrada.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "El perfil predeterminado no se puede sobreescribir", + "DialogProfileDeleteProfileTitle": "Eliminando perfil", + "DialogProfileDeleteProfileMessage": "Esta acción es irreversible, ¿estás seguro de querer continuar?", + "DialogWarning": "Advertencia", + "DialogPPTCDeletionMessage": "Vas a borrar la caché de PPTC para:\n\n{0}\n\n¿Estás seguro de querer continuar?", + "DialogPPTCDeletionErrorMessage": "Error purgando la caché de PPTC en {0}: {1}", + "DialogShaderDeletionMessage": "Vas a borrar la caché de sombreadores para:\n\n{0}\n\n¿Estás seguro de querer continuar?", + "DialogShaderDeletionErrorMessage": "Error purgando la caché de sombreadores en {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx ha encontrado un error", + "DialogInvalidTitleIdErrorMessage": "Error de interfaz: El juego seleccionado no tiene una ID válida", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "No se pudo encontrar un firmware válido en {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Instalar firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Se instalará la versión de sistema {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nEsto reemplazará la versión de sistema actual, {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n¿Continuar?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Instalando firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Versión de sistema {0} instalada con éxito.", + "DialogUserProfileDeletionWarningMessage": "Si eliminas el perfil seleccionado no quedará ningún otro perfil", + "DialogUserProfileDeletionConfirmMessage": "¿Quieres eliminar el perfil seleccionado?", + "DialogControllerSettingsModifiedConfirmMessage": "Se ha actualizado la configuración del mando actual.", + "DialogControllerSettingsModifiedConfirmSubMessage": "¿Guardar cambios?", + "DialogLoadNcaErrorMessage": "{0}. Archivo con error: {1}", + "DialogDlcNoDlcErrorMessage": "¡Ese archivo no contiene contenido descargable para el título seleccionado!", + "DialogPerformanceCheckLoggingEnabledMessage": "Has habilitado los registros debug, diseñados solo para uso de los desarrolladores.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Para un rendimiento óptimo, se recomienda deshabilitar los registros debug. ¿Quieres deshabilitarlos ahora?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Has habilitado el volcado de sombras, diseñado solo para uso de los desarrolladores.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Para un rendimiento óptimo, se recomienda deshabilitar el volcado de sombraa. ¿Quieres deshabilitarlo ahora?", + "DialogLoadAppGameAlreadyLoadedMessage": "Ya has cargado un juego", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Por favor, detén la emulación o cierra el emulador antes de iniciar otro juego.", + "DialogUpdateAddUpdateErrorMessage": "¡Ese archivo no contiene una actualización para el título seleccionado!", + "DialogSettingsBackendThreadingWarningTitle": "Advertencia - multihilado de gráficos", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx debe reiniciarse para aplicar este cambio. Dependiendo de tu plataforma, puede que tengas que desactivar manualmente la optimización enlazada de tus controladores gráficos para usar el multihilo de Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Funcionalidades", + "SettingsTabGraphicsBackendMultithreading": "Multihilado del motor gráfico:", + "CommonAuto": "Automático", + "CommonOff": "Desactivado", + "CommonOn": "Activado", + "InputDialogYes": "Sí", + "InputDialogNo": "No", + "DialogProfileInvalidProfileNameErrorMessage": "El nombre de archivo contiene caracteres inválidos. Por favor, inténtalo de nuevo.", + "MenuBarOptionsPauseEmulation": "Pausar", + "MenuBarOptionsResumeEmulation": "Reanudar", + "AboutUrlTooltipMessage": "Haz clic para abrir el sitio web de Ryujinx en tu navegador predeterminado.", + "AboutDisclaimerMessage": "Ryujinx no tiene afiliación alguna con Nintendo™,\nni con ninguno de sus socios.", + "AboutAmiiboDisclaimerMessage": "Utilizamos AmiiboAPI (www.amiiboapi.com)\nen nuestra emulación de Amiibo.", + "AboutPatreonUrlTooltipMessage": "Haz clic para abrir el Patreon de Ryujinx en tu navegador predeterminado.", + "AboutGithubUrlTooltipMessage": "Haz clic para abrir el GitHub de Ryujinx en tu navegador predeterminado.", + "AboutDiscordUrlTooltipMessage": "Haz clic para recibir una invitación al Discord de Ryujinx en tu navegador predeterminado.", + "AboutTwitterUrlTooltipMessage": "Haz clic para abrir el Twitter de Ryujinx en tu navegador predeterminado.", + "AboutRyujinxAboutTitle": "Acerca de:", + "AboutRyujinxAboutContent": "Ryujinx es un emulador para Nintendo Switch™.\nPor favor, apóyanos en Patreon.\nEncuentra las noticias más recientes en nuestro Twitter o Discord.\nDesarrolladores interesados en contribuir pueden encontrar más información en GitHub o Discord.", + "AboutRyujinxMaintainersTitle": "Mantenido por:", + "AboutRyujinxMaintainersContentTooltipMessage": "Haz clic para abrir la página de contribuidores en tu navegador predeterminado.", + "AboutRyujinxSupprtersTitle": "Apoyado en Patreon Por:", + "AmiiboSeriesLabel": "Serie de Amiibo", + "AmiiboCharacterLabel": "Personaje", + "AmiiboScanButtonLabel": "Escanear", + "AmiiboOptionsShowAllLabel": "Mostrar todos los Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack: usar etiqueta aleatoria Uuid", + "DlcManagerTableHeadingEnabledLabel": "Habilitado", + "DlcManagerTableHeadingTitleIdLabel": "ID de título", + "DlcManagerTableHeadingContainerPathLabel": "Directorio del contenedor", + "DlcManagerTableHeadingFullPathLabel": "Directorio completo", + "DlcManagerRemoveAllButton": "Quitar todo", + "DlcManagerEnableAllButton": "Activar todas", + "DlcManagerDisableAllButton": "Desactivar todos", + "MenuBarOptionsChangeLanguage": "Cambiar idioma", + "CommonSort": "Orden", + "CommonShowNames": "Mostrar nombres", + "CommonFavorite": "Favorito", + "OrderAscending": "Ascendente", + "OrderDescending": "Descendente", + "SettingsTabGraphicsFeatures": "Funcionalidades Y Mejoras", + "ErrorWindowTitle": "Ventana de error", + "ToggleDiscordTooltip": "Elige si muestras Ryujinx o no en tu actividad de Discord cuando lo estés usando", + "AddGameDirBoxTooltip": "Elige un directorio de juegos para mostrar en la ventana principal", + "AddGameDirTooltip": "Agrega un directorio de juegos a la lista", + "RemoveGameDirTooltip": "Quita el directorio seleccionado de la lista", + "CustomThemeCheckTooltip": "Activa o desactiva los temas personalizados para la interfaz", + "CustomThemePathTooltip": "Carpeta que contiene los temas personalizados para la interfaz", + "CustomThemeBrowseTooltip": "Busca un tema personalizado para la interfaz", + "DockModeToggleTooltip": "El modo dock o modo TV hace que la consola emulada se comporte como una Nintendo Switch en su dock. Esto mejora la calidad gráfica en la mayoría de los juegos. Del mismo modo, si lo desactivas, el sistema emulado se comportará como una Nintendo Switch en modo portátil, reduciendo la cálidad de los gráficos.\n\nConfigura los controles de \"Jugador\" 1 si planeas jugar en modo dock/TV; configura los controles de \"Portátil\" si planeas jugar en modo portátil.\n\nActívalo si no sabes qué hacer.", + "DirectKeyboardTooltip": "Activa o desactiva \"soporte para acceso directo al teclado (HID)\" (Permite a los juegos utilizar tu teclado como entrada de texto)", + "DirectMouseTooltip": "Activa o desactiva \"soporte para acceso directo al ratón (HID)\" (Permite a los juegos utilizar tu ratón como cursor)", + "RegionTooltip": "Cambia la región del sistema", + "LanguageTooltip": "Cambia el idioma del sistema", + "TimezoneTooltip": "Cambia la zona horaria del sistema", + "TimeTooltip": "Cambia la hora del sistema", + "VSyncToggleTooltip": "Sincronización vertical del sistema emulado. A efectos prácticos es un límite de fotogramas para la mayoría de juegos; deshabilitarlo puede hacer que los juegos se aceleren o que las pantallas de carga tarden más o se atasquen.\n\nPuedes activar y desactivar esto mientras el emulador está funcionando con un atajo de teclado a tu elección. Recomendamos hacer esto en vez de deshabilitarlo.\n\nActívalo si no sabes qué hacer.", + "PptcToggleTooltip": "Guarda funciones de JIT traducidas para que no sea necesario traducirlas cada vez que el juego carga.\n\nReduce los tirones y acelera significativamente el tiempo de inicio de los juegos después de haberlos ejecutado al menos una vez.\n\nActívalo si no sabes qué hacer.", + "FsIntegrityToggleTooltip": "Comprueba si hay archivos corruptos en los juegos que ejecutes al abrirlos, y si detecta archivos corruptos, muestra un error de Hash en los registros.\n\nEsto no tiene impacto alguno en el rendimiento y está pensado para ayudar a resolver problemas.\n\nActívalo si no sabes qué hacer.", + "AudioBackendTooltip": "Cambia el motor usado para renderizar audio.\n\nSDL2 es el preferido, mientras que OpenAL y SoundIO se usan si hay problemas con este. Dummy no produce audio.\n\nSelecciona SDL2 si no sabes qué hacer.", + "MemoryManagerTooltip": "Cambia la forma de mapear y acceder a la memoria del guest. Afecta en gran medida al rendimiento de la CPU emulada.\n\nSelecciona \"Host sin verificación\" si no sabes qué hacer.", + "MemoryManagerSoftwareTooltip": "Usa una tabla de paginación de software para traducir direcciones. Ofrece la precisión más exacta pero el rendimiento más lento.", + "MemoryManagerHostTooltip": "Mapea la memoria directamente en la dirección de espacio del host. Compilación y ejecución JIT mucho más rápida.", + "MemoryManagerUnsafeTooltip": "Mapea la memoria directamente, pero no enmascara la dirección dentro del espacio de dirección del guest antes del acceso. El modo más rápido, pero a costa de seguridad. La aplicación guest puede acceder a la memoria desde cualquier parte en Ryujinx, así que ejecuta solo programas en los que confíes cuando uses este modo.", + "DRamTooltip": "Expande la memoria DRAM del sistema emulado de 4GiB a 6GiB.\n\nUtilizar solo con packs de texturas HD o mods de resolución 4K. NO mejora el rendimiento.\n\nDesactívalo si no sabes qué hacer.", + "IgnoreMissingServicesTooltip": "Hack para ignorar servicios no implementados del Horizon OS. Esto puede ayudar a sobrepasar crasheos cuando inicies ciertos juegos.\n\nDesactívalo si no sabes qué hacer.", + "GraphicsBackendThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio multihilado. Rendimiento máximo ligeramente superior en controladores gráficos que soporten multihilado.\n\nSelecciona \"Auto\" si no sabes qué hacer.", + "GalThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio multihilado. Rendimiento máximo ligeramente superior en controladores gráficos que soporten multihilado.\n\nSelecciona \"Auto\" si no sabes qué hacer.", + "ShaderCacheToggleTooltip": "Guarda una caché de sombreadores en disco, la cual reduce los tirones a medida que vas jugando.\n\nActívalo si no sabes qué hacer.", + "ResolutionScaleTooltip": "Escala de resolución aplicada a objetivos aplicables en el renderizado", + "ResolutionScaleEntryTooltip": "Escalado de resolución de coma flotante, como por ejemplo 1,5. Los valores no íntegros pueden causar errores gráficos o crashes.", + "AnisotropyTooltip": "Nivel de filtrado anisotrópico (selecciona Auto para utilizar el valor solicitado por el juego)", + "AspectRatioTooltip": "Relación de aspecto aplicada a la ventana de renderizado.", + "ShaderDumpPathTooltip": "Directorio en el cual se volcarán los sombreadores de los gráficos", + "FileLogTooltip": "Guarda los registros de la consola en archivos en disco. No afectan al rendimiento.", + "StubLogTooltip": "Escribe mensajes de Stub en la consola. No afectan al rendimiento.", + "InfoLogTooltip": "Escribe mensajes de Info en la consola. No afectan al rendimiento.", + "WarnLogTooltip": "Escribe mensajes de Advertencia en la consola. No afectan al rendimiento.", + "ErrorLogTooltip": "Escribe mensajes de Error en la consola. No afectan al rendimiento.", + "TraceLogTooltip": "Escribe mensajes de Rastro en la consola. No afectan al rendimiento.", + "GuestLogTooltip": "Escribe mensajes de Guest en la consola. No afectan al rendimiento.", + "FileAccessLogTooltip": "Activa mensajes de acceso a archivo en la consola", + "FSAccessLogModeTooltip": "Activa registros FS Access en la consola. Los modos posibles son entre 0 y 3", + "DeveloperOptionTooltip": "Usar con cuidado", + "OpenGlLogLevel": "Requiere activar los niveles de registro apropiados", + "DebugLogTooltip": "Escribe mensajes de debug en la consola\n\nActiva esto solo si un miembro del equipo te lo pide expresamente, pues hará que el registro sea difícil de leer y empeorará el rendimiento del emulador.", + "LoadApplicationFileTooltip": "Abre el explorador de archivos para elegir un archivo compatible con Switch para cargar", + "LoadApplicationFolderTooltip": "Abre el explorador de archivos para elegir un archivo desempaquetado y compatible con Switch para cargar", + "OpenRyujinxFolderTooltip": "Abre la carpeta de sistema de Ryujinx", + "OpenRyujinxLogsTooltip": "Abre la carpeta en la que se guardan los registros", + "ExitTooltip": "Cierra Ryujinx", + "OpenSettingsTooltip": "Abre la ventana de configuración", + "OpenProfileManagerTooltip": "Abre la ventana para gestionar los perfiles de usuario", + "StopEmulationTooltip": "Detiene la emulación del juego actual y regresa a la selección de juegos", + "CheckUpdatesTooltip": "Busca actualizaciones para Ryujinx", + "OpenAboutTooltip": "Abre la ventana \"Acerca de\"", + "GridSize": "Tamaño de cuadrícula", + "GridSizeTooltip": "Cambia el tamaño de los objetos en la cuadrícula", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portugués brasileño", + "AboutRyujinxContributorsButtonHeader": "Ver todos los contribuidores", + "SettingsTabSystemAudioVolume": "Volumen: ", + "AudioVolumeTooltip": "Ajusta el nivel de volumen", + "SettingsTabSystemEnableInternetAccess": "Conectar guest a Internet/Modo LAN", + "EnableInternetAccessTooltip": "Permite a la aplicación emulada conectarse a Internet.\n\nLos juegos que tengan modo LAN podrán conectarse entre sí habilitando esta opción y estando conectados al mismo módem. Asimismo, esto permite conexiones con consolas reales.\n\nNO permite conectar con los servidores de Nintendo Online. Puede causar que ciertos juegos crasheen al intentar conectarse a sus servidores.\n\nDesactívalo si no estás seguro.", + "GameListContextMenuManageCheatToolTip": "Activa o desactiva los cheats", + "GameListContextMenuManageCheat": "Administrar cheats", + "ControllerSettingsStickRange": "Alcance:", + "DialogStopEmulationTitle": "Ryujinx - Detener emulación", + "DialogStopEmulationMessage": "¿Seguro que quieres detener la emulación actual?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Sonido", + "SettingsTabNetwork": "Red", + "SettingsTabNetworkConnection": "Conexión de red", + "SettingsTabCpuCache": "Caché de CPU", + "SettingsTabCpuMemory": "Memoria de CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Por favor, actualiza Ryujinx a través de FlatHub.", + "UpdaterDisabledWarningTitle": "¡Actualizador deshabilitado!", + "GameListContextMenuOpenSdModsDirectory": "Abrir carpeta de mods Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Abre la carpeta alternativa de mods en la que puedes insertar mods de Atmosphere. Útil para mods que vengan organizados para uso en consola.", + "ControllerSettingsRotate90": "Rotar 90° en el sentido de las agujas del reloj", + "IconSize": "Tamaño de iconos", + "IconSizeTooltip": "Cambia el tamaño de los iconos de juegos", + "MenuBarOptionsShowConsole": "Mostrar consola", + "ShaderCachePurgeError": "Error al eliminar la caché en {0}: {1}", + "UserErrorNoKeys": "No se encontraron keys", + "UserErrorNoFirmware": "No se encontró firmware", + "UserErrorFirmwareParsingFailed": "Error al analizar el firmware", + "UserErrorApplicationNotFound": "No se encontró la aplicación", + "UserErrorUnknown": "Error desconocido", + "UserErrorUndefined": "Error indefinido", + "UserErrorNoKeysDescription": "Ryujinx no pudo encontrar tus 'prod.keys'.", + "UserErrorNoFirmwareDescription": "Ryujinx no pudo encontrar un firmware instalado.", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx no pudo analizar el firmware. Normalmente esto ocurre debido a keys desfasadas.", + "UserErrorApplicationNotFoundDescription": "Ryujinx no pudo encontrar una aplicación válida en ese camino.", + "UserErrorUnknownDescription": "¡Ocurrió un error desconocido!", + "UserErrorUndefinedDescription": "¡Ocurrió un error indefinido! Esto no debería pasar, por favor, ¡contacta con un desarrollador!", + "OpenSetupGuideMessage": "Abrir la guía de instalación", + "NoUpdate": "No actualizado", + "TitleUpdateVersionLabel": "Versión {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Confirmación", + "FileDialogAllTypes": "Todos los tipos", + "Never": "Nunca", + "SwkbdMinCharacters": "Debe tener al menos {0} caracteres", + "SwkbdMinRangeCharacters": "Debe tener {0}-{1} caracteres", + "SoftwareKeyboard": "Teclado de software", + "DialogControllerAppletMessagePlayerRange": "La aplicación require {0} jugador(es) con:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Por favor abre las opciones y reconfigura los dispositivos de entrada o presiona 'Cerrar'.", + "DialogControllerAppletMessage": "La aplicación require exactamente {0} jugador(es) con:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Por favor abre las opciones y reconfigura los dispositivos de entrada o presiona 'Cerrar'.", + "DialogControllerAppletDockModeSet": "Modo dock/TV activo. El control portátil también es inválido.\n\n", + "UpdaterRenaming": "Renombrando archivos viejos...", + "UpdaterRenameFailed": "El actualizador no pudo renombrar el archivo: {0}", + "UpdaterAddingFiles": "Añadiendo nuevos archivos...", + "UpdaterExtracting": "Extrayendo actualización...", + "UpdaterDownloading": "Descargando actualización...", + "Game": "Juego", + "Docked": "Dock/TV", + "Handheld": "Portátil", + "ConnectionError": "Error de conexión.", + "AboutPageDeveloperListMore": "{0} y más...", + "ApiError": "Error de API.", + "LoadingHeading": "Cargando {0}", + "CompilingPPTC": "Compilando PTC", + "CompilingShaders": "Compilando sombreadores", + "AllKeyboards": "Todos los teclados", + "OpenFileDialogTitle": "Selecciona un archivo soportado para cargar", + "OpenFolderDialogTitle": "Selecciona una carpeta con un juego desempaquetado", + "AllSupportedFormats": "Todos los formatos soportados", + "RyujinxUpdater": "Actualizador de Ryujinx", + "SettingsTabHotkeys": "Atajos de teclado", + "SettingsTabHotkeysHotkeys": "Atajos de teclado", + "SettingsTabHotkeysToggleVsyncHotkey": "Alternar la sincronización vertical:", + "SettingsTabHotkeysScreenshotHotkey": "Captura de pantalla:", + "SettingsTabHotkeysShowUiHotkey": "Mostrar interfaz:", + "SettingsTabHotkeysPauseHotkey": "Pausar:", + "SettingsTabHotkeysToggleMuteHotkey": "Silenciar:", + "ControllerMotionTitle": "Opciones de controles de movimiento", + "ControllerRumbleTitle": "Opciones de vibración", + "SettingsSelectThemeFileDialogTitle": "Selecciona un archivo de tema", + "SettingsXamlThemeFile": "Archivo de tema Xaml", + "AvatarWindowTitle": "Administrar cuentas - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Desconocido", + "Usage": "Uso", + "Writable": "Escribible", + "SelectDlcDialogTitle": "Selecciona archivo(s) de DLC", + "SelectUpdateDialogTitle": "Selecciona archivo(s) de actualización", + "UserProfileWindowTitle": "Administrar perfiles de usuario", + "CheatWindowTitle": "Administrar cheats", + "DlcWindowTitle": "Administrar contenido descargable", + "UpdateWindowTitle": "Administrar actualizaciones", + "CheatWindowHeading": "Cheats disponibles para {0} [{1}]", + "DlcWindowHeading": "Contenido descargable disponible para {0} [{1}]", + "UserProfilesEditProfile": "Editar selección", + "Cancel": "Cancelar", + "Save": "Guardar", + "Discard": "Descartar", + "UserProfilesSetProfileImage": "Elegir Imagen de Perfil ", + "UserProfileEmptyNameError": "El nombre es obligatorio", + "UserProfileNoImageError": "Debe establecerse la imagen de perfil", + "GameUpdateWindowHeading": "Actualizaciones disponibles para {0} [{1}]", + "SettingsTabHotkeysResScaleUpHotkey": "Aumentar la resolución:", + "SettingsTabHotkeysResScaleDownHotkey": "Disminuir la resolución:", + "UserProfilesName": "Nombre:", + "UserProfilesUserId": "Id de Usuario:", + "SettingsTabGraphicsBackend": "Fondo de gráficos", + "SettingsTabGraphicsBackendTooltip": "Graphics Backend to use", + "SettingsEnableTextureRecompression": "Enable Texture Recompression", + "SettingsEnableTextureRecompressionTooltip": "Compresses certain textures in order to reduce VRAM usage.\n\nRecommended for use with GPUs that have less than 4GiB VRAM.\n\nLeave OFF if unsure.", + "SettingsTabGraphicsPreferredGpu": "Preferred GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Select the graphics card that will be used with the Vulkan graphics backend.\n\nDoes not affect the GPU that OpenGL will use.\n\nSet to the GPU flagged as \"dGPU\" if unsure. If there isn't one, leave untouched.", + "SettingsAppRequiredRestartMessage": "Ryujinx Restart Required", + "SettingsGpuBackendRestartMessage": "Graphics Backend or GPU settings have been modified. This will require a restart to be applied", + "SettingsGpuBackendRestartSubMessage": "Do you want to restart now?", + "RyujinxUpdaterMessage": "Do you want to update Ryujinx to the latest version?", + "SettingsTabHotkeysVolumeUpHotkey": "Increase Volume:", + "SettingsTabHotkeysVolumeDownHotkey": "Decrease Volume:", + "SettingsEnableMacroHLE": "Enable Macro HLE", + "SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Manage Saves", + "DeleteUserSave": "Do you want to delete user save for this game?", + "IrreversibleActionNote": "This action is not reversible.", + "SaveManagerHeading": "Manage Saves for {0}", + "SaveManagerTitle": "Save Manager", + "Name": "Name", + "Size": "Size", + "Search": "Search", + "UserProfilesRecoverLostAccounts": "Recover Lost Accounts", + "Recover": "Recover", + "UserProfilesRecoverHeading": "Saves were found for the following accounts" +} diff --git a/src/Ryujinx.Ava/Assets/Locales/fr_FR.json b/src/Ryujinx.Ava/Assets/Locales/fr_FR.json new file mode 100644 index 00000000..fad46751 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/fr_FR.json @@ -0,0 +1,614 @@ +{ + "Language": "Français", + "MenuBarFileOpenApplet": "Ouvrir Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Ouvrir l'Applet Mii Editor en mode Standalone", + "SettingsTabInputDirectMouseAccess": "Accès direct à la souris", + "SettingsTabSystemMemoryManagerMode": "Mode de gestion mémoire :", + "SettingsTabSystemMemoryManagerModeSoftware": "Logiciel", + "SettingsTabSystemMemoryManagerModeHost": "Hôte (rapide)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Hôte non vérifié (plus rapide, non sécurisé)", + "MenuBarFile": "_Fichier", + "MenuBarFileOpenFromFile": "_Charger un jeu depuis un fichier", + "MenuBarFileOpenUnpacked": "Charger un jeu _extrait", + "MenuBarFileOpenEmuFolder": "Ouvrir le dossier Ryujinx", + "MenuBarFileOpenLogsFolder": "Ouvrir le dossier des journaux", + "MenuBarFileExit": "_Quitter", + "MenuBarOptions": "Options", + "MenuBarOptionsToggleFullscreen": "Basculer en plein écran", + "MenuBarOptionsStartGamesInFullscreen": "Démarrer le jeu en plein écran", + "MenuBarOptionsStopEmulation": "Arrêter l'émulation", + "MenuBarOptionsSettings": "_Paramètres", + "MenuBarOptionsManageUserProfiles": "_Gêrer les profils d'utilisateurs", + "MenuBarActions": "_Actions", + "MenuBarOptionsSimulateWakeUpMessage": "Simuler une sortie de veille", + "MenuBarActionsScanAmiibo": "Scanner un Amiibo", + "MenuBarTools": "_Outils", + "MenuBarToolsInstallFirmware": "Installer un firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Installer un firmware depuis un fichier XCI ou ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Installer un firmware depuis un dossier", + "MenuBarHelp": "Aide", + "MenuBarHelpCheckForUpdates": "Vérifier les mises à jour", + "MenuBarHelpAbout": "Á propos", + "MenuSearch": "Rechercher...", + "GameListHeaderFavorite": "Favoris", + "GameListHeaderIcon": "Icône", + "GameListHeaderApplication": "Application", + "GameListHeaderDeveloper": "Développeur", + "GameListHeaderVersion": "Version", + "GameListHeaderTimePlayed": "Temps de jeu", + "GameListHeaderLastPlayed": "Dernière partie", + "GameListHeaderFileExtension": "Extension du Fichier", + "GameListHeaderFileSize": "Taille du Fichier", + "GameListHeaderPath": "Chemin", + "GameListContextMenuOpenUserSaveDirectory": "Ouvrir le dossier de sauvegarde utilisateur", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde utilisateur du jeu", + "GameListContextMenuOpenDeviceSaveDirectory": "Ouvrir le dossier de sauvegarde console", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde console du jeu", + "GameListContextMenuOpenBcatSaveDirectory": "Ouvrir le dossier de sauvegarde BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Ouvre le dossier contenant la sauvegarde BCAT du jeu", + "GameListContextMenuManageTitleUpdates": "Gérer les mises à jour", + "GameListContextMenuManageTitleUpdatesToolTip": "Ouvre la fenêtre de gestion des mises à jour", + "GameListContextMenuManageDlc": "Gérer les DLC", + "GameListContextMenuManageDlcToolTip": "Ouvre la fenêtre de gestion des DLC", + "GameListContextMenuOpenModsDirectory": "Ouvrir le dossier des Mods", + "GameListContextMenuOpenModsDirectoryToolTip": "Ouvre le dossier contenant les mods du jeu", + "GameListContextMenuCacheManagement": "Gestion des caches", + "GameListContextMenuCacheManagementPurgePptc": "Purger le PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Supprime le PPTC du jeu", + "GameListContextMenuCacheManagementPurgeShaderCache": "Purger le cache des Shaders", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Supprime le cache des shaders du jeu", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Ouvrir le dossier du PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Ouvre le dossier contenant le PPTC du jeu", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Ouvrir le dossier du cache des shaders", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Ouvre le dossier contenant le cache des shaders du jeu", + "GameListContextMenuExtractData": "Extraire les données", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extrait la section ExeFS du jeu (mise à jour incluse)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extrait la section RomFS du jeu (mise à jour incluse)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Extrait la section Logo du jeu (mise à jour incluse)", + "StatusBarGamesLoaded": "{0}/{1} Jeux chargés", + "StatusBarSystemVersion": "Version du Firmware: {0}", + "Settings": "Paramètres", + "SettingsTabGeneral": "Général", + "SettingsTabGeneralGeneral": "Général", + "SettingsTabGeneralEnableDiscordRichPresence": "Active Discord Rich Presence", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Vérifier les mises à jour au démarrage", + "SettingsTabGeneralShowConfirmExitDialog": "Afficher le message de \"Confirmation de fermeture\"", + "SettingsTabGeneralHideCursorOnIdle": "Masquer le curseur si inactif", + "SettingsTabGeneralGameDirectories": "Dossiers de Jeux", + "SettingsTabGeneralAdd": "Ajouter", + "SettingsTabGeneralRemove": "Supprimer", + "SettingsTabSystem": "Système", + "SettingsTabSystemCore": "Cœur", + "SettingsTabSystemSystemRegion": "Région du système:", + "SettingsTabSystemSystemRegionJapan": "Japon", + "SettingsTabSystemSystemRegionUSA": "USA", + "SettingsTabSystemSystemRegionEurope": "Europe", + "SettingsTabSystemSystemRegionAustralia": "Australie", + "SettingsTabSystemSystemRegionChina": "Chine", + "SettingsTabSystemSystemRegionKorea": "Corée", + "SettingsTabSystemSystemRegionTaiwan": "Taïwan", + "SettingsTabSystemSystemLanguage": "Langue du système:", + "SettingsTabSystemSystemLanguageJapanese": "Japonais", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Américain", + "SettingsTabSystemSystemLanguageFrench": "Français", + "SettingsTabSystemSystemLanguageGerman": "Allemand", + "SettingsTabSystemSystemLanguageItalian": "Italien", + "SettingsTabSystemSystemLanguageSpanish": "Espagnol", + "SettingsTabSystemSystemLanguageChinese": "Chinois", + "SettingsTabSystemSystemLanguageKorean": "Coréen", + "SettingsTabSystemSystemLanguageDutch": "Néerlandais", + "SettingsTabSystemSystemLanguagePortuguese": "Portugais", + "SettingsTabSystemSystemLanguageRussian": "Russe", + "SettingsTabSystemSystemLanguageTaiwanese": "Taïwanais", + "SettingsTabSystemSystemLanguageBritishEnglish": "Anglais", + "SettingsTabSystemSystemLanguageCanadianFrench": "Canadien", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Espagnol latino-américain", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chinois simplifié", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Chinois traditionnel", + "SettingsTabSystemSystemTimeZone": "Fuseau horaire du système:", + "SettingsTabSystemSystemTime": "Heure du système:", + "SettingsTabSystemEnableVsync": "Activer la VSync", + "SettingsTabSystemEnablePptc": "Activer le PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Activer la vérification de l'intégrité du système de fichiers", + "SettingsTabSystemAudioBackend": "Bibliothèque Audio :", + "SettingsTabSystemAudioBackendDummy": "Factice", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": " (Cela peut causer des instabilités)", + "SettingsTabSystemExpandDramSize": "Augmenter la taille de la DRAM à 6GiB", + "SettingsTabSystemIgnoreMissingServices": "Ignorer les services manquant", + "SettingsTabGraphics": "Graphique", + "SettingsTabGraphicsAPI": "API Graphique", + "SettingsTabGraphicsEnableShaderCache": "Activer le cache des shaders", + "SettingsTabGraphicsAnisotropicFiltering": "Filtrage anisotrope:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "x2", + "SettingsTabGraphicsAnisotropicFiltering4x": "x4", + "SettingsTabGraphicsAnisotropicFiltering8x": "x8", + "SettingsTabGraphicsAnisotropicFiltering16x": "x16", + "SettingsTabGraphicsResolutionScale": "Échelle de résolution:", + "SettingsTabGraphicsResolutionScaleCustom": "Customisée (Non recommandée)", + "SettingsTabGraphicsResolutionScaleNative": "Natif (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "x2 (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "x3 (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "x4 (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Format:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Écran étiré", + "SettingsTabGraphicsDeveloperOptions": "Options développeur", + "SettingsTabGraphicsShaderDumpPath": "Chemin du dossier de dump des shaders:", + "SettingsTabLogging": "Journaux", + "SettingsTabLoggingLogging": "Journaux", + "SettingsTabLoggingEnableLoggingToFile": "Activer la sauvegarde des journaux vers un fichier", + "SettingsTabLoggingEnableStubLogs": "Activer les journaux stub", + "SettingsTabLoggingEnableInfoLogs": "Activer les journaux d'informations", + "SettingsTabLoggingEnableWarningLogs": "Activer les journaux d'avertissements", + "SettingsTabLoggingEnableErrorLogs": "Activer les journaux d'erreurs", + "SettingsTabLoggingEnableTraceLogs": "Activer journaux d'erreurs Trace", + "SettingsTabLoggingEnableGuestLogs": "Activer les journaux du programme simulé", + "SettingsTabLoggingEnableFsAccessLogs": "Activer les journaux des accès au système de fichiers", + "SettingsTabLoggingFsGlobalAccessLogMode": "Niveau des journaux des accès au système de fichiers:", + "SettingsTabLoggingDeveloperOptions": "Options développeur (ATTENTION: Cela peut réduire les performances)", + "SettingsTabLoggingGraphicsBackendLogLevel": "Niveau du journal du backend graphique :", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Aucun", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Erreur", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Ralentissements", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Tout", + "SettingsTabLoggingEnableDebugLogs": "Activer les journaux de debug", + "SettingsTabInput": "Contrôles", + "SettingsTabInputEnableDockedMode": "Active le mode station d'accueil", + "SettingsTabInputDirectKeyboardAccess": "Accès direct au clavier", + "SettingsButtonSave": "Enregistrer", + "SettingsButtonClose": "Fermer", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Annuler", + "SettingsButtonApply": "Appliquer", + "ControllerSettingsPlayer": "Joueur", + "ControllerSettingsPlayer1": "Joueur 1", + "ControllerSettingsPlayer2": "Joueur 2", + "ControllerSettingsPlayer3": "Joueur 3", + "ControllerSettingsPlayer4": "Joueur 4", + "ControllerSettingsPlayer5": "Joueur 5", + "ControllerSettingsPlayer6": "Joueur 6", + "ControllerSettingsPlayer7": "Joueur 7", + "ControllerSettingsPlayer8": "Joueur 8", + "ControllerSettingsHandheld": "Portable", + "ControllerSettingsInputDevice": "Périphériques", + "ControllerSettingsRefresh": "Actualiser", + "ControllerSettingsDeviceDisabled": "Désactivé", + "ControllerSettingsControllerType": "Type de Controleur", + "ControllerSettingsControllerTypeHandheld": "Portable", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon Joints", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Gauche", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon Droite", + "ControllerSettingsProfile": "Profil", + "ControllerSettingsProfileDefault": "Défaut", + "ControllerSettingsLoad": "Charger", + "ControllerSettingsAdd": "Ajouter", + "ControllerSettingsRemove": "Supprimer", + "ControllerSettingsButtons": "Boutons", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Croix Directionnelle", + "ControllerSettingsDPadUp": "Haut", + "ControllerSettingsDPadDown": "Bas", + "ControllerSettingsDPadLeft": "Gauche", + "ControllerSettingsDPadRight": "Droite", + "ControllerSettingsLStick": "Joystick Gauche", + "ControllerSettingsLStickButton": "Bouton", + "ControllerSettingsLStickUp": "Haut", + "ControllerSettingsLStickDown": "Bas", + "ControllerSettingsLStickLeft": "Gauche", + "ControllerSettingsLStickRight": "Droite", + "ControllerSettingsLStickStick": "Joystick", + "ControllerSettingsLStickInvertXAxis": "Inverser l'axe X", + "ControllerSettingsLStickInvertYAxis": "Inverser l'axe Y", + "ControllerSettingsLStickDeadzone": "Zone morte:", + "ControllerSettingsRStick": "Joystick Droit", + "ControllerSettingsRStickButton": "Bouton", + "ControllerSettingsRStickUp": "Haut", + "ControllerSettingsRStickDown": "Bas", + "ControllerSettingsRStickLeft": "Gauche", + "ControllerSettingsRStickRight": "Droite", + "ControllerSettingsRStickStick": "Joystick", + "ControllerSettingsRStickInvertXAxis": "Inverser l'axe X", + "ControllerSettingsRStickInvertYAxis": "Inverser l'axe Y", + "ControllerSettingsRStickDeadzone": "Zone morte:", + "ControllerSettingsTriggersLeft": "Gachettes Gauche", + "ControllerSettingsTriggersRight": "Gachettes Droite", + "ControllerSettingsTriggersButtonsLeft": "Boutons Gachettes Gauche", + "ControllerSettingsTriggersButtonsRight": "Boutons Gachettes Droite", + "ControllerSettingsTriggers": "Gachettes", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Boutons Gauche", + "ControllerSettingsExtraButtonsRight": "Boutons Droite", + "ControllerSettingsMisc": "Divers", + "ControllerSettingsTriggerThreshold": "Seuil de gachettes:", + "ControllerSettingsMotion": "Mouvements", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Utiliser un capteur de mouvements CemuHook", + "ControllerSettingsMotionControllerSlot": "Contrôleur ID:", + "ControllerSettingsMotionMirrorInput": "Inverser les contrôles", + "ControllerSettingsMotionRightJoyConSlot": "JoyCon Droit ID:", + "ControllerSettingsMotionServerHost": "Addresse du Server:", + "ControllerSettingsMotionGyroSensitivity": "Sensibilitée du gyroscope:", + "ControllerSettingsMotionGyroDeadzone": "Zone morte du gyroscope:", + "ControllerSettingsSave": "Enregistrer", + "ControllerSettingsClose": "Fermer", + "UserProfilesSelectedUserProfile": "Choisir un profil utilisateur:", + "UserProfilesSaveProfileName": "Enregistrer le nom du profil", + "UserProfilesChangeProfileImage": "Changer l'image du profil", + "UserProfilesAvailableUserProfiles": "Profils utilisateurs disponible:", + "UserProfilesAddNewProfile": "Ajouter un nouveau profil", + "UserProfilesDelete": "Supprimer", + "UserProfilesClose": "Fermer", + "ProfileImageSelectionTitle": "Sélection de l'image du profil", + "ProfileImageSelectionHeader": "Choisir l'image du profil", + "ProfileImageSelectionNote": "Vous pouvez importer une image de profil personnalisée ou sélectionner un avatar à partir du firmware", + "ProfileImageSelectionImportImage": "Importer une image", + "ProfileImageSelectionSelectAvatar": "Choisir un avatar du firmware", + "InputDialogTitle": "Fenêtre d'entrée de texte", + "InputDialogOk": "OK", + "InputDialogCancel": "Annuler", + "InputDialogAddNewProfileTitle": "Choisir un nom de profil", + "InputDialogAddNewProfileHeader": "Merci d'entrer un nom de profil", + "InputDialogAddNewProfileSubtext": "(Longueur max.: {0})", + "AvatarChoose": "Choisir", + "AvatarSetBackgroundColor": "Choisir une couleur de fond", + "AvatarClose": "Fermer", + "ControllerSettingsLoadProfileToolTip": "Charger un profil", + "ControllerSettingsAddProfileToolTip": "Ajouter un profil", + "ControllerSettingsRemoveProfileToolTip": "Supprimer un profil", + "ControllerSettingsSaveProfileToolTip": "Enregistrer un profil", + "MenuBarFileToolsTakeScreenshot": "Prendre une Capture d'Écran", + "MenuBarFileToolsHideUi": "Masquer l'interface utilisateur", + "GameListContextMenuToggleFavorite": "Ajouter/Retirer des favoris", + "GameListContextMenuToggleFavoriteToolTip": "Activer/désactiver le statut favori du jeu", + "SettingsTabGeneralTheme": "Thème", + "SettingsTabGeneralThemeCustomTheme": "Chemin du thème personnalisé", + "SettingsTabGeneralThemeBaseStyle": "Style par défaut", + "SettingsTabGeneralThemeBaseStyleDark": "Sombre", + "SettingsTabGeneralThemeBaseStyleLight": "Lumière", + "SettingsTabGeneralThemeEnableCustomTheme": "Activer un Thème Personnalisé", + "ButtonBrowse": "Parcourir", + "ControllerSettingsConfigureGeneral": "Configurer", + "ControllerSettingsRumble": "Vibreur", + "ControllerSettingsRumbleStrongMultiplier": "Multiplicateur de vibrations fortes", + "ControllerSettingsRumbleWeakMultiplier": "Multiplicateur de vibrations faibles", + "DialogMessageSaveNotAvailableMessage": "Il n'y a aucune sauvegarde pour {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Voulez-vous créer une sauvegarde pour ce jeu ?", + "DialogConfirmationTitle": "Ryujinx - Confirmation", + "DialogUpdaterTitle": "Ryujinx - Mise à Jour", + "DialogErrorTitle": "Ryujinx - Erreur", + "DialogWarningTitle": "Ryujinx - Avertissement", + "DialogExitTitle": "Ryujinx - Quitter", + "DialogErrorMessage": "Ryujinx a rencontré une erreur", + "DialogExitMessage": "Êtes-vous sûr de vouloir fermer Ryujinx ?", + "DialogExitSubMessage": "Toute progression non sauvegardée sera perdue.", + "DialogMessageCreateSaveErrorMessage": "Une erreur s'est produite lors de la création de la sauvegarde spécifiée : {0}", + "DialogMessageFindSaveErrorMessage": "Une erreur s'est produite lors de la recherche de la sauvegarde spécifiée : {0}", + "FolderDialogExtractTitle": "Choisissez le dossier dans lequel extraire", + "DialogNcaExtractionMessage": "Extraction de la section {0} depuis {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Extracteur de la section NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Échec de l'extraction. Le NCA principal n'était pas présent dans le fichier sélectionné.", + "DialogNcaExtractionCheckLogErrorMessage": "Échec de l'extraction. Lisez le fichier journal pour plus d'informations.", + "DialogNcaExtractionSuccessMessage": "Extraction terminée avec succès.", + "DialogUpdaterConvertFailedMessage": "Échec de la conversion de la version actuelle de Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Annuler la mise à jour!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Vous utilisez déjà la version la plus mise à jour de Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Une erreur s'est produite lors de la tentative d'obtention des informations de publication de la version GitHub. Cela peut survenir lorsqu'une nouvelle version est en cours de compilation par GitHub Actions. Réessayez dans quelques minutes.", + "DialogUpdaterConvertFailedGithubMessage": "Impossible de convertir la version reçue de Ryujinx depuis Github Release.", + "DialogUpdaterDownloadingMessage": "Téléchargement de la mise à jour...", + "DialogUpdaterExtractionMessage": "Extraction de la mise à jour…", + "DialogUpdaterRenamingMessage": "Renommage de la mise à jour...", + "DialogUpdaterAddingFilesMessage": "Ajout d'une nouvelle mise à jour...", + "DialogUpdaterCompleteMessage": "Mise à jour terminée !", + "DialogUpdaterRestartMessage": "Voulez-vous redémarrer Ryujinx maintenant ?", + "DialogUpdaterArchNotSupportedMessage": "Vous n'utilisez pas d'architecture système prise en charge !", + "DialogUpdaterArchNotSupportedSubMessage": "(Seuls les systèmes x64 sont pris en charge !)", + "DialogUpdaterNoInternetMessage": "Vous n'êtes pas connecté à Internet !", + "DialogUpdaterNoInternetSubMessage": "Veuillez vérifier que vous avez une connexion Internet fonctionnelle!", + "DialogUpdaterDirtyBuildMessage": "Vous ne pouvez pas mettre à jour une version Dirty de Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Veuillez télécharger Ryujinx sur https://ryujinx.org/ si vous recherchez une version prise en charge.", + "DialogRestartRequiredMessage": "Redémarrage Requis", + "DialogThemeRestartMessage": "Le thème a été enregistré. Un redémarrage est requis pour appliquer le thème.", + "DialogThemeRestartSubMessage": "Voulez-vous redémarrer", + "DialogFirmwareInstallEmbeddedMessage": "Voulez-vous installer le firmware intégré dans ce jeu ? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Aucun firmware installé n'a été trouvé mais Ryujinx a pu installer le firmware {0} à partir du jeu fourni.\\nL'émulateur va maintenant démarrer.", + "DialogFirmwareNoFirmwareInstalledMessage": "Aucun Firmware installé", + "DialogFirmwareInstalledMessage": "Le firmware {0} a été installé", + "DialogOpenSettingsWindowLabel": "Ouvrir la fenêtre de configuration", + "DialogControllerAppletTitle": "Controller Applet", + "DialogMessageDialogErrorExceptionMessage": "Erreur lors de l'affichage de la boîte de dialogue : {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Erreur lors de l'affichage du clavier logiciel: {0}", + "DialogErrorAppletErrorExceptionMessage": "Erreur lors de l'affichage de la boîte de dialogue ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nPour plus d'informations sur la manière de corriger cette erreur, suivez notre Guide d'Installation.", + "DialogUserErrorDialogTitle": "Erreur Ryujinx ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Une erreur est survenue lors de la récupération des informations de l'API.", + "DialogAmiiboApiConnectErrorMessage": "Impossible de se connecter au serveur API Amiibo. Le service est peut-être hors service ou vous devriez peut-être vérifier que votre connexion internet est connectée.", + "DialogProfileInvalidProfileErrorMessage": "Le profil {0} est incompatible avec le système de configuration de manette actuel.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Le profil par défaut ne peut pas être écrasé", + "DialogProfileDeleteProfileTitle": "Supprimer le profil", + "DialogProfileDeleteProfileMessage": "Cette action est irréversible, êtes-vous sûr de vouloir continuer ?", + "DialogWarning": "Avertissement", + "DialogPPTCDeletionMessage": "Vous êtes sur le point de mettre en file d'attente une reconstruction PPTC au prochain démarrage de :\n\n{0}\n\nÊtes-vous sûr de vouloir continuer ?", + "DialogPPTCDeletionErrorMessage": "Erreur lors de la purge du cache PPTC à {0}: {1}", + "DialogShaderDeletionMessage": "Vous êtes sur le point de supprimer le cache du Shader pour :\n\n{0}\n\nÊtes-vous sûr de vouloir continuer ?", + "DialogShaderDeletionErrorMessage": "Erreur lors de la purge du cache du Shader à {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx a rencontré une erreur", + "DialogInvalidTitleIdErrorMessage": "Erreur d'UI : le jeu sélectionné n'a pas d'ID de titre valide", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Un firmware valide n'a pas été trouvé dans {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Installer le Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "La version {0} du système sera installée.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nCela remplacera la version actuelle du système {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nVoulez-vous continuer ?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Installation du firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Version du système {0} installée avec succès.", + "DialogUserProfileDeletionWarningMessage": "Il n'y aurait aucun autre profil à ouvrir si le profil sélectionné est supprimé", + "DialogUserProfileDeletionConfirmMessage": "Voulez-vous supprimer le profil sélectionné ?", + "DialogControllerSettingsModifiedConfirmMessage": "Les paramètres actuels du contrôleur ont été mis à jour.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Voulez-vous sauvegarder?", + "DialogLoadNcaErrorMessage": "{0}. Fichier erroné : {1}", + "DialogDlcNoDlcErrorMessage": "Le fichier spécifié ne contient pas de DLC pour le titre sélectionné !", + "DialogPerformanceCheckLoggingEnabledMessage": "Vous avez activé la journalisation des traces, conçue pour être utilisée uniquement par les développeurs.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Pour des performances optimales, il est recommandé de désactiver la journalisation des traces. Souhaitez-vous désactiver la journalisation des traces maintenant ?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Vous avez activé l'extraction des shaders, qui est conçu pour être utilisé par les développeurs uniquement.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Pour des performances optimales, il est recommandé de désactiver l'extraction des shaders. Souhaitez-vous désactiver l'extraction des shaders maintenant ?", + "DialogLoadAppGameAlreadyLoadedMessage": "Un jeu a déjà été chargé", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Veuillez arrêter l'émulation ou fermer l'émulateur avant de lancer un autre jeu.", + "DialogUpdateAddUpdateErrorMessage": "Le fichier spécifié ne contient pas de mise à jour pour le titre sélectionné !", + "DialogSettingsBackendThreadingWarningTitle": "Avertissement - Backend Threading ", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx doit être redémarré après avoir changé cette option pour qu'elle s'applique complètement. Selon votre plate-forme, vous devrez peut-être désactiver manuellement le multithreading de votre pilote lorsque vous utilisez Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Fonctionnalités", + "SettingsTabGraphicsBackendMultithreading": "Interface graphique multithread", + "CommonAuto": "Auto", + "CommonOff": "Désactivé", + "CommonOn": "Activé", + "InputDialogYes": "Oui", + "InputDialogNo": "Non", + "DialogProfileInvalidProfileNameErrorMessage": "Le nom du fichier contient des caractères invalides. Veuillez réessayer.", + "MenuBarOptionsPauseEmulation": "Suspendre", + "MenuBarOptionsResumeEmulation": "Reprendre", + "AboutUrlTooltipMessage": "Cliquez pour ouvrir le site de Ryujinx dans votre navigateur par défaut.", + "AboutDisclaimerMessage": "Ryujinx n'est pas affilié à Nintendo™,\nou à aucun de ses partenaires, de quelque manière que ce soit.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) est utilisé\ndans notre émulation Amiibo.", + "AboutPatreonUrlTooltipMessage": "Cliquez pour ouvrir la page Patreon de Ryujinx dans votre navigateur par défaut.", + "AboutGithubUrlTooltipMessage": "Cliquez pour ouvrir la page GitHub de Ryujinx dans votre navigateur par défaut.", + "AboutDiscordUrlTooltipMessage": "Cliquez pour ouvrir une invitation au serveur Discord de Ryujinx dans votre navigateur par défaut.", + "AboutTwitterUrlTooltipMessage": "Cliquez pour ouvrir la page Twitter de Ryujinx dans votre navigateur par défaut.", + "AboutRyujinxAboutTitle": "Á propos:", + "AboutRyujinxAboutContent": "Ryujinx est un émulateur pour la Nintendo Switch™.\nMerci de nous soutenir sur Patreon.\nObtenez toutes les dernières actualités sur notre Twitter ou notre Discord.\nLes développeurs intéressés à contribuer peuvent en savoir plus sur notre GitHub ou notre Discord.", + "AboutRyujinxMaintainersTitle": "Maintenu par :", + "AboutRyujinxMaintainersContentTooltipMessage": "Cliquez pour ouvrir la page Contributeurs dans votre navigateur par défaut.", + "AboutRyujinxSupprtersTitle": "Supporté sur Patreon par :", + "AmiiboSeriesLabel": "Séries Amiibo", + "AmiiboCharacterLabel": "Personnage", + "AmiiboScanButtonLabel": "Scanner", + "AmiiboOptionsShowAllLabel": "Afficher tous les Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack : Utiliser un tag Uuid aléatoire", + "DlcManagerTableHeadingEnabledLabel": "Activé", + "DlcManagerTableHeadingTitleIdLabel": "ID du titre", + "DlcManagerTableHeadingContainerPathLabel": "Chemin du conteneur", + "DlcManagerTableHeadingFullPathLabel": "Chemin complet", + "DlcManagerRemoveAllButton": "Tout supprimer", + "DlcManagerEnableAllButton": "Activer Tout", + "DlcManagerDisableAllButton": "Désactiver Tout", + "MenuBarOptionsChangeLanguage": "Changer la Langue", + "CommonSort": "Trier", + "CommonShowNames": "Afficher les noms", + "CommonFavorite": "Favoris", + "OrderAscending": "Croissant", + "OrderDescending": "Décroissant", + "SettingsTabGraphicsFeatures": "Fonctionnalités & Améliorations", + "ErrorWindowTitle": "Fenêtre d'erreur", + "ToggleDiscordTooltip": "Choisissez d'afficher ou non Ryujinx sur votre activité « en cours de jeu » Discord", + "AddGameDirBoxTooltip": "Entrez un répertoire de jeux à ajouter à la liste", + "AddGameDirTooltip": "Ajouter un répertoire de jeux à la liste", + "RemoveGameDirTooltip": "Supprimer le dossier sélectionné", + "CustomThemeCheckTooltip": "Use a custom Avalonia theme for the GUI to change the appearance of the emulator menus", + "CustomThemePathTooltip": "Chemin vers le thème personnalisé de l'interface utilisateur", + "CustomThemeBrowseTooltip": "Parcourir vers un thème personnalisé pour l'interface utilisateur", + "DockModeToggleTooltip": "Le mode station d'accueil permet à la console émulée de se comporter comme une Nintendo Switch en mode station d'accueil, ce qui améliore la fidélité graphique dans la plupart des jeux. Inversement, la désactivation de cette option rendra la console émulée comme une console Nintendo Switch portable, réduisant la qualité graphique.\n\nConfigurer les controles du joueur 1 si vous prévoyez d'utiliser le mode station d'accueil; configurez les commandes portable si vous prévoyez d'utiliser le mode portable.\n\nLaissez ACTIVER si vous n'êtes pas sûr.", + "DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.", + "DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.", + "RegionTooltip": "Changer la région du système", + "LanguageTooltip": "Changer la langue du système", + "TimezoneTooltip": "Changer le fuseau horaire du système", + "TimeTooltip": "Changer l'heure du système", + "VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference. We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.", + "PptcToggleTooltip": "Saves translated JIT functions so that they do not need to be translated every time the game loads.\n\nReduces stuttering and significantly speeds up boot times after the first boot of a game.\n\nLeave ON if unsure.", + "FsIntegrityToggleTooltip": "Checks for corrupt files when booting a game, and if corrupt files are detected, displays a hash error in the log.\n\nHas no impact on performance and is meant to help troubleshooting.\n\nLeave ON if unsure.", + "AudioBackendTooltip": "Changes the backend used to render audio.\n\nSDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound.\n\nSet to SDL2 if unsure.", + "MemoryManagerTooltip": "Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance.\n\nSet to HOST UNCHECKED if unsure.", + "MemoryManagerSoftwareTooltip": "Use a software page table for address translation. Highest accuracy but slowest performance.", + "MemoryManagerHostTooltip": "Directly map memory in the host address space. Much faster JIT compilation and execution.", + "MemoryManagerUnsafeTooltip": "Directly map memory, but do not mask the address within the guest address space before access. Faster, but at the cost of safety. The guest application can access memory from anywhere in Ryujinx, so only run programs you trust with this mode.", + "DRamTooltip": "Utilizes an alternative MemoryMode layout to mimic a Switch development model.\n\nThis is only useful for higher-resolution texture packs or 4k resolution mods. Does NOT improve performance.\n\nLeave OFF if unsure.", + "IgnoreMissingServicesTooltip": "Ignores unimplemented Horizon OS services. This may help in bypassing crashes when booting certain games.\n\nLeave OFF if unsure.", + "GraphicsBackendThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", + "GalThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.", + "ShaderCacheToggleTooltip": "Saves a disk shader cache which reduces stuttering in subsequent runs.\n\nLeave ON if unsure.", + "ResolutionScaleTooltip": "Resolution Scale applied to applicable render targets", + "ResolutionScaleEntryTooltip": "Floating point resolution scale, such as 1.5. Non-integral scales are more likely to cause issues or crash.", + "AnisotropyTooltip": "Level of Anisotropic Filtering (set to Auto to use the value requested by the game)", + "AspectRatioTooltip": "Ratio d'aspect appliqué à la fenêtre de rendu", + "ShaderDumpPathTooltip": "Chemin de copie des Shaders Graphiques", + "FileLogTooltip": "Sauver le journal de la console dans un fichier journal sur le disque. Cela n'affecte pas les performances.", + "StubLogTooltip": "Prints stub log messages in the console. Does not affect performance.", + "InfoLogTooltip": "Prints info log messages in the console. Does not affect performance.", + "WarnLogTooltip": "Prints warning log messages in the console. Does not affect performance.", + "ErrorLogTooltip": "Prints error log messages in the console. Does not affect performance.", + "TraceLogTooltip": "Prints trace log messages in the console. Does not affect performance.", + "GuestLogTooltip": "Prints guest log messages in the console. Does not affect performance.", + "FileAccessLogTooltip": "Prints file access log messages in the console.", + "FSAccessLogModeTooltip": "Enables FS access log output to the console. Possible modes are 0-3", + "DeveloperOptionTooltip": "Utiliser avec précaution", + "OpenGlLogLevel": "Nécessite l'activation des niveaux de journalisation appropriés", + "DebugLogTooltip": "Prints debug log messages in the console.\n\nOnly use this if specifically instructed by a staff member, as it will make logs difficult to read and worsen emulator performance.", + "LoadApplicationFileTooltip": "Ouvrir un explorateur de fichiers pour choisir un fichier compatible Switch à charger", + "LoadApplicationFolderTooltip": "Ouvrir un explorateur de fichiers pour choisir une application Switch compatible et décompressée à charger", + "OpenRyujinxFolderTooltip": "Ouvrir le dossier du système de fichiers Ryujinx", + "OpenRyujinxLogsTooltip": "Ouvre le dossier dans lequel les journaux sont écrits", + "ExitTooltip": "Quitter Ryujinx", + "OpenSettingsTooltip": "Ouvrir la fenêtre de configuration", + "OpenProfileManagerTooltip": "Ouvrir la fenêtre du gestionnaire de profils d'utilisateurs", + "StopEmulationTooltip": "Arrêter l'émulation du jeu en cours et revenir à la sélection des jeux", + "CheckUpdatesTooltip": "Vérifier les mises à jour de Ryujinx", + "OpenAboutTooltip": "Ouvrir la fenêtre À Propos", + "GridSize": "Taille de la grille", + "GridSizeTooltip": "Modifier la taille des éléments de la grille", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portugais brésilien", + "AboutRyujinxContributorsButtonHeader": "Voir tous les contributeurs", + "SettingsTabSystemAudioVolume": "Volume :", + "AudioVolumeTooltip": "Modifier le volume audio", + "SettingsTabSystemEnableInternetAccess": "Accès Internet Invité/Mode LAN", + "EnableInternetAccessTooltip": "Permet à l'application émulée de se connecter à Internet.\n\nLes jeux avec un mode LAN peuvent se connecter les uns aux autres lorsque cette option est cochée et que les systèmes sont connectés au même point d'accès. Cela inclut également les vrais consoles.\n\nCette option n'autorise PAS la connexion aux serveurs Nintendo. Elle peut faire planter certains jeux qui essaient de se connecter à l'Internet.\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.", + "GameListContextMenuManageCheatToolTip": "Gérer la triche", + "GameListContextMenuManageCheat": "Gérer la triche", + "ControllerSettingsStickRange": "Intervalle:", + "DialogStopEmulationTitle": "Ryujinx - Arrêt de l'émulation", + "DialogStopEmulationMessage": "Êtes-vous sûr de vouloir arrêter l'émulation ?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Audio", + "SettingsTabNetwork": "Réseau", + "SettingsTabNetworkConnection": "Connexion réseau", + "SettingsTabCpuCache": "Cache CPU", + "SettingsTabCpuMemory": "Mémoire CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Merci de mettre à jour Ryujinx via FlatHub.", + "UpdaterDisabledWarningTitle": "Mise à jour désactivée !", + "GameListContextMenuOpenSdModsDirectory": "Ouvrir le dossier Mods d'Atmosphère", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Ouvre le répertoire alternatif de carte SD Atmosphère qui contient les Mods d'Application. Utile pour les mods qui sont conçu pour le vrai matériel.", + "ControllerSettingsRotate90": "Rotation 90° horaire", + "IconSize": "Taille d'icône", + "IconSizeTooltip": "Changer la taille des icônes de jeu", + "MenuBarOptionsShowConsole": "Afficher la console", + "ShaderCachePurgeError": "Erreur lors de la purge du cache du Shader à {0}: {1}", + "UserErrorNoKeys": "Clés introuvables", + "UserErrorNoFirmware": "Firmware introuvable", + "UserErrorFirmwareParsingFailed": "Erreur d'analyse du firmware", + "UserErrorApplicationNotFound": " Application introuvable", + "UserErrorUnknown": "Erreur inconnue", + "UserErrorUndefined": "Erreur non définie", + "UserErrorNoKeysDescription": "Ryujinx n'a pas trouvé votre fichier 'prod.keys'", + "UserErrorNoFirmwareDescription": "Ryujinx n'a pas trouvé de firmwares installés", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx n'a pas pu analyser le firmware fourni. Cela est généralement dû à des clés obsolètes.", + "UserErrorApplicationNotFoundDescription": "Ryujinx n'a pas pu trouver une application valide dans le chemin indiqué.", + "UserErrorUnknownDescription": "Une erreur inconnue est survenue!", + "UserErrorUndefinedDescription": "Une erreur inconnue est survenue ! Cela ne devrait pas se produire, merci de contacter un développeur !", + "OpenSetupGuideMessage": "Ouvrir le guide d'installation", + "NoUpdate": "Aucune mise à jour", + "TitleUpdateVersionLabel": "Version {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Confirmation", + "FileDialogAllTypes": "Tous les types", + "Never": "Jamais", + "SwkbdMinCharacters": "Doit comporter au moins {0} caractères", + "SwkbdMinRangeCharacters": "Doit contenir {0}-{1} caractères en longueur", + "SoftwareKeyboard": "Clavier logiciel", + "DialogControllerAppletMessagePlayerRange": "L'application demande {0} joueur(s) avec :\n\nTYPES : {1}\n\nJOUEURS : {2}\n\n{3}Merci d'ouvrir les Paramètres et de reconfigurer les Périphériques maintenant ou appuyez sur Fermer.", + "DialogControllerAppletMessage": "L'application demande exactement {0} joueur(s) avec :\n\nTYPES : {1}\n\nJOUEURS : {2}\n\n{3}Merci d'ouvrir les Paramètres et de reconfigurer les Périphériques maintenant ou appuyez sur Fermer.", + "DialogControllerAppletDockModeSet": "Mode station d'accueil défini. Le portable est également invalide.\n\n", + "UpdaterRenaming": "Renommage des anciens fichiers...", + "UpdaterRenameFailed": "Impossible de renommer le fichier : {0}", + "UpdaterAddingFiles": "Ajout des nouveaux fichiers...", + "UpdaterExtracting": "Extraction de la mise à jour…", + "UpdaterDownloading": "Téléchargement de la mise à jour...", + "Game": "Jeu", + "Docked": "Attaché", + "Handheld": "Portable", + "ConnectionError": "Erreur de connexion.", + "AboutPageDeveloperListMore": "{0} et plus...", + "ApiError": "Erreur API.", + "LoadingHeading": "Chargement {0}", + "CompilingPPTC": "Compilation PTC", + "CompilingShaders": "Compilation des Shaders", + "AllKeyboards": "Tous les claviers", + "OpenFileDialogTitle": "Sélectionnez un fichier supporté à ouvrir", + "OpenFolderDialogTitle": "Sélectionnez un dossier avec un jeu décompressé", + "AllSupportedFormats": "Tous les formats supportés", + "RyujinxUpdater": "Mise à jour de Ryujinx", + "SettingsTabHotkeys": "Raccourcis clavier", + "SettingsTabHotkeysHotkeys": "Raccourcis clavier", + "SettingsTabHotkeysToggleVsyncHotkey": "Activer/désactiver la VSync :", + "SettingsTabHotkeysScreenshotHotkey": "Captures d'écran :", + "SettingsTabHotkeysShowUiHotkey": "Afficher UI :", + "SettingsTabHotkeysPauseHotkey": "Suspendre :", + "SettingsTabHotkeysToggleMuteHotkey": "Muet : ", + "ControllerMotionTitle": "Réglages du contrôle par mouvement", + "ControllerRumbleTitle": "Paramètres de Vibration", + "SettingsSelectThemeFileDialogTitle": "Sélectionnez un Fichier de Thème", + "SettingsXamlThemeFile": "Fichier thème Xaml", + "AvatarWindowTitle": "Gérer les Comptes - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Inconnu", + "Usage": "Utilisation", + "Writable": "Ecriture possible", + "SelectDlcDialogTitle": "Sélectionner les fichiers DLC", + "SelectUpdateDialogTitle": "Sélectionner les fichiers de mise à jour", + "UserProfileWindowTitle": "Gestionnaire de profils utilisateur", + "CheatWindowTitle": "Gestionnaire de triches", + "DlcWindowTitle": "Gestionnaire de contenus téléchargeables", + "UpdateWindowTitle": "Gestionnaire de mises à jour", + "CheatWindowHeading": "Cheats disponibles pour {0} [{1}]", + "DlcWindowHeading": "{0} Contenu(s) téléchargeable(s) disponible pour {1} ({2})", + "UserProfilesEditProfile": "Éditer la sélection", + "Cancel": "Annuler", + "Save": "Enregistrer", + "Discard": "Abandonner", + "UserProfilesSetProfileImage": "Modifier l'image du profil", + "UserProfileEmptyNameError": "Le nom est requis", + "UserProfileNoImageError": "L'image du profil doit être définie", + "GameUpdateWindowHeading": "{0} mise(s) à jour disponible pour {1} ({2})", + "SettingsTabHotkeysResScaleUpHotkey": "Augmenter la résolution:", + "SettingsTabHotkeysResScaleDownHotkey": "Diminuer la résolution :", + "UserProfilesName": "Nom :", + "UserProfilesUserId": "Identifiant de l'utilisateur :", + "SettingsTabGraphicsBackend": "API de Rendu", + "SettingsTabGraphicsBackendTooltip": "Interface Graphique à utiliser", + "SettingsEnableTextureRecompression": "Activer la recompression des textures", + "SettingsEnableTextureRecompressionTooltip": "Compresse certaines textures afin de réduire l'utilisation de la VRAM.\n\nRecommandé pour une utilisation avec des GPU qui ont moins de 4 Go de VRAM.\n\nLaissez DÉSACTIVÉ si vous n'êtes pas sûr.", + "SettingsTabGraphicsPreferredGpu": "GPU préféré", + "SettingsTabGraphicsPreferredGpuTooltip": "Sélectionnez la carte graphique qui sera utilisée avec l'interface graphique Vulkan.\n\nCela ne change pas le GPU qu'OpenGL utilisera.\n\nChoisissez le GPU noté \"dGPU\" si vous n'êtes pas sûr. S'il n'y en a pas, ne pas modifier.", + "SettingsAppRequiredRestartMessage": "Redémarrage de Ryujinx requis", + "SettingsGpuBackendRestartMessage": "Les paramètres de l'interface graphique ou du GPU ont été modifiés. Cela nécessitera un redémarrage pour être appliqué", + "SettingsGpuBackendRestartSubMessage": "\n\nVoulez-vous redémarrer maintenant?", + "RyujinxUpdaterMessage": "Voulez-vous mettre à jour Ryujinx vers la dernière version ?", + "SettingsTabHotkeysVolumeUpHotkey": "Augmenter le volume :", + "SettingsTabHotkeysVolumeDownHotkey": "Diminuer le volume :", + "SettingsEnableMacroHLE": "Activer les macros HLE", + "SettingsEnableMacroHLETooltip": "Émulation de haut niveau du code de Macro GPU.\n\nAméliore les performances, mais peut causer des bugs graphiques dans certains jeux.\n\nLaissez ACTIVER si vous n'êtes pas sûr.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Gérer les sauvegardes", + "DeleteUserSave": "Voulez-vous supprimer la sauvegarde de l'utilisateur pour ce jeu?", + "IrreversibleActionNote": "Cette action n'est pas réversible.", + "SaveManagerHeading": "Gérer les sauvegardes pour {0}", + "SaveManagerTitle": "Gestionnaire de sauvegarde", + "Name": "Nom ", + "Size": "Taille", + "Search": "Rechercher", + "UserProfilesRecoverLostAccounts": "Récupérer les comptes perdus", + "Recover": "Récupérer", + "UserProfilesRecoverHeading": "Des sauvegardes ont été trouvées pour les comptes suivants" +} diff --git a/src/Ryujinx.Ava/Assets/Locales/it_IT.json b/src/Ryujinx.Ava/Assets/Locales/it_IT.json new file mode 100644 index 00000000..0ae02749 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/it_IT.json @@ -0,0 +1,614 @@ +{ + "Language": "Italiano", + "MenuBarFileOpenApplet": "Apri Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Apri l'applet Mii Editor in modalità Standalone", + "SettingsTabInputDirectMouseAccess": "Accesso diretto al mouse", + "SettingsTabSystemMemoryManagerMode": "Modalità di gestione della memoria:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Host (veloce)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (più veloce, non sicura)", + "MenuBarFile": "_File", + "MenuBarFileOpenFromFile": "_Carica applicazione da un file", + "MenuBarFileOpenUnpacked": "Carica _gioco estratto", + "MenuBarFileOpenEmuFolder": "Apri cartella di Ryujinx", + "MenuBarFileOpenLogsFolder": "Apri cartella dei logs", + "MenuBarFileExit": "_Esci", + "MenuBarOptions": "Opzioni", + "MenuBarOptionsToggleFullscreen": "Schermo intero", + "MenuBarOptionsStartGamesInFullscreen": "Avvia i giochi a schermo intero", + "MenuBarOptionsStopEmulation": "Ferma emulazione", + "MenuBarOptionsSettings": "_Impostazioni", + "MenuBarOptionsManageUserProfiles": "_Gestisci i profili utente", + "MenuBarActions": "_Azioni", + "MenuBarOptionsSimulateWakeUpMessage": "Simula messaggio Wake-up", + "MenuBarActionsScanAmiibo": "Scansiona un Amiibo", + "MenuBarTools": "_Strumenti", + "MenuBarToolsInstallFirmware": "Installa firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Installa un firmware da file XCI o ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Installa un firmare da una cartella", + "MenuBarHelp": "Aiuto", + "MenuBarHelpCheckForUpdates": "Controlla aggiornamenti", + "MenuBarHelpAbout": "Informazioni", + "MenuSearch": "Cerca...", + "GameListHeaderFavorite": "Pref", + "GameListHeaderIcon": "Icona", + "GameListHeaderApplication": "Nome", + "GameListHeaderDeveloper": "Sviluppatore", + "GameListHeaderVersion": "Versione", + "GameListHeaderTimePlayed": "Tempo di gioco", + "GameListHeaderLastPlayed": "Giocato l'ultima volta", + "GameListHeaderFileExtension": "Estensione", + "GameListHeaderFileSize": "Dimensione file", + "GameListHeaderPath": "Percorso", + "GameListContextMenuOpenUserSaveDirectory": "Apri la cartella salvataggi dell'utente", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Apre la cartella che contiene i salvataggi di gioco dell'utente", + "GameListContextMenuOpenDeviceSaveDirectory": "Apri la cartella dispositivo dell'utente", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Apre la cartella che contiene i salvataggi di gioco del dispositivo", + "GameListContextMenuOpenBcatSaveDirectory": "Apri la cartella BCAT dell'utente", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Apre la cartella che contiene i salvataggi BCAT dell'applicazione", + "GameListContextMenuManageTitleUpdates": "Gestisci aggiornamenti del gioco", + "GameListContextMenuManageTitleUpdatesToolTip": "Apre la finestra di gestione aggiornamenti del gioco", + "GameListContextMenuManageDlc": "Gestici DLC", + "GameListContextMenuManageDlcToolTip": "Apre la finestra di gestione DLC", + "GameListContextMenuOpenModsDirectory": "Apri cartella delle mod", + "GameListContextMenuOpenModsDirectoryToolTip": "Apre la cartella che contiene le mod dell'applicazione", + "GameListContextMenuCacheManagement": "Gestione della cache", + "GameListContextMenuCacheManagementPurgePptc": "Pulisci PPTC cache", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Elimina la PPTC cache dell'applicazione", + "GameListContextMenuCacheManagementPurgeShaderCache": "Pulisci shader cache", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Elimina la shader cache dell'applicazione", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Apri cartella PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Apre la cartella che contiene la PPTC cache dell'applicazione", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Apri cartella shader cache", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Apre la cartella che contiene la shader cache dell'applicazione", + "GameListContextMenuExtractData": "Estrai dati", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Estrae la sezione ExeFS dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Estrae la sezione RomFS dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Estrae la sezione Logo dall'attuale configurazione dell'applicazione (includendo aggiornamenti)", + "StatusBarGamesLoaded": "{0}/{1} Giochi caricati", + "StatusBarSystemVersion": "Versione di sistema: {0}", + "Settings": "Impostazioni", + "SettingsTabGeneral": "Interfaccia utente", + "SettingsTabGeneralGeneral": "Generali", + "SettingsTabGeneralEnableDiscordRichPresence": "Attiva Discord Rich Presence", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Controlla aggiornamenti all'avvio", + "SettingsTabGeneralShowConfirmExitDialog": "Mostra dialogo \"Conferma Uscita\"", + "SettingsTabGeneralHideCursorOnIdle": "Nascondi cursore inattivo", + "SettingsTabGeneralGameDirectories": "Cartelle dei giochi", + "SettingsTabGeneralAdd": "Aggiungi", + "SettingsTabGeneralRemove": "Rimuovi", + "SettingsTabSystem": "Sistema", + "SettingsTabSystemCore": "Principale", + "SettingsTabSystemSystemRegion": "Regione del sistema:", + "SettingsTabSystemSystemRegionJapan": "Giappone", + "SettingsTabSystemSystemRegionUSA": "Stati Uniti d'America", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Australia", + "SettingsTabSystemSystemRegionChina": "Cina", + "SettingsTabSystemSystemRegionKorea": "Corea", + "SettingsTabSystemSystemRegionTaiwan": "Tailandia", + "SettingsTabSystemSystemLanguage": "Lingua del sistema:", + "SettingsTabSystemSystemLanguageJapanese": "Giapponese", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Inglese americano", + "SettingsTabSystemSystemLanguageFrench": "Francese", + "SettingsTabSystemSystemLanguageGerman": "Tedesco", + "SettingsTabSystemSystemLanguageItalian": "Italiano", + "SettingsTabSystemSystemLanguageSpanish": "Spagnolo", + "SettingsTabSystemSystemLanguageChinese": "Cinese", + "SettingsTabSystemSystemLanguageKorean": "Coreano", + "SettingsTabSystemSystemLanguageDutch": "Olandese", + "SettingsTabSystemSystemLanguagePortuguese": "Portoghese", + "SettingsTabSystemSystemLanguageRussian": "Russo", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanese", + "SettingsTabSystemSystemLanguageBritishEnglish": "Inglese britannico", + "SettingsTabSystemSystemLanguageCanadianFrench": "Francese canadese", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Spagnolo latino americano", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Cinese semplificato", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Cinese tradizionale", + "SettingsTabSystemSystemTimeZone": "Fuso orario del sistema:", + "SettingsTabSystemSystemTime": "Data e ora del sistema:", + "SettingsTabSystemEnableVsync": "Attiva VSync", + "SettingsTabSystemEnablePptc": "Attiva PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Attiva controlli d'integrità FS", + "SettingsTabSystemAudioBackend": "Backend audio:", + "SettingsTabSystemAudioBackendDummy": "Manichino", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "AudioIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Trucchi", + "SettingsTabSystemHacksNote": " (Possono causare instabilità)", + "SettingsTabSystemExpandDramSize": "Espandi dimensione DRAM a 6GiB", + "SettingsTabSystemIgnoreMissingServices": "Ignora servizi mancanti", + "SettingsTabGraphics": "Grafica", + "SettingsTabGraphicsAPI": "API Grafiche", + "SettingsTabGraphicsEnableShaderCache": "Attiva Shader Cache", + "SettingsTabGraphicsAnisotropicFiltering": "Filtro anisotropico:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Scala della risoluzione:", + "SettingsTabGraphicsResolutionScaleCustom": "Personalizzata (Non raccomandata)", + "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Rapporto d'aspetto:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Adatta alla finestra", + "SettingsTabGraphicsDeveloperOptions": "Opzioni da sviluppatore", + "SettingsTabGraphicsShaderDumpPath": "Percorso di dump degli shaders:", + "SettingsTabLogging": "Log", + "SettingsTabLoggingLogging": "Log", + "SettingsTabLoggingEnableLoggingToFile": "Salva i log su file", + "SettingsTabLoggingEnableStubLogs": "Attiva Stub Logs", + "SettingsTabLoggingEnableInfoLogs": "Attiva Info Logs", + "SettingsTabLoggingEnableWarningLogs": "Attiva Warning Logs", + "SettingsTabLoggingEnableErrorLogs": "Attiva Error Logs", + "SettingsTabLoggingEnableTraceLogs": "Attiva Trace Logs", + "SettingsTabLoggingEnableGuestLogs": "Attiva Guest Logs", + "SettingsTabLoggingEnableFsAccessLogs": "Attiva Fs Access Logs", + "SettingsTabLoggingFsGlobalAccessLogMode": "Modalità log accesso globale Fs:", + "SettingsTabLoggingDeveloperOptions": "Opzioni da sviluppatore (AVVISO: Ridurrà le prestazioni)", + "SettingsTabLoggingGraphicsBackendLogLevel": "Livello registro backend grafico:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Nessuno", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Errore", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Rallentamenti", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Tutto", + "SettingsTabLoggingEnableDebugLogs": "Attiva logs di debug", + "SettingsTabInput": "Input", + "SettingsTabInputEnableDockedMode": "Attiva modalità TV", + "SettingsTabInputDirectKeyboardAccess": "Accesso diretto alla tastiera", + "SettingsButtonSave": "Salva", + "SettingsButtonClose": "Chiudi", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Cancella", + "SettingsButtonApply": "Applica", + "ControllerSettingsPlayer": "Giocatore", + "ControllerSettingsPlayer1": "Giocatore 1", + "ControllerSettingsPlayer2": "Giocatore 2", + "ControllerSettingsPlayer3": "Giocatore 3", + "ControllerSettingsPlayer4": "Giocatore 4", + "ControllerSettingsPlayer5": "Giocatore 5", + "ControllerSettingsPlayer6": "Giocatore 6", + "ControllerSettingsPlayer7": "Giocatore 7", + "ControllerSettingsPlayer8": "Giocatore 8", + "ControllerSettingsHandheld": "Portatile", + "ControllerSettingsInputDevice": "Dispositivo di input", + "ControllerSettingsRefresh": "Ricarica", + "ControllerSettingsDeviceDisabled": "Disabilitato", + "ControllerSettingsControllerType": "Tipo di controller", + "ControllerSettingsControllerTypeHandheld": "Portatile", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "Coppia di JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon sinistro", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon destro", + "ControllerSettingsProfile": "Profilo", + "ControllerSettingsProfileDefault": "Predefinito", + "ControllerSettingsLoad": "Carica", + "ControllerSettingsAdd": "Aggiungi", + "ControllerSettingsRemove": "Rimuovi", + "ControllerSettingsButtons": "Pulsanti", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Croce direzionale", + "ControllerSettingsDPadUp": "Su", + "ControllerSettingsDPadDown": "Giù", + "ControllerSettingsDPadLeft": "Sinistra", + "ControllerSettingsDPadRight": "Destra", + "ControllerSettingsLStick": "Stick sinistro", + "ControllerSettingsLStickButton": "Pulsante", + "ControllerSettingsLStickUp": "Su", + "ControllerSettingsLStickDown": "Giù", + "ControllerSettingsLStickLeft": "Sinistra", + "ControllerSettingsLStickRight": "Destra", + "ControllerSettingsLStickStick": "Levetta", + "ControllerSettingsLStickInvertXAxis": "Inverti stick X", + "ControllerSettingsLStickInvertYAxis": "Inverti stick Y", + "ControllerSettingsLStickDeadzone": "Zona morta:", + "ControllerSettingsRStick": "Stick destro", + "ControllerSettingsRStickButton": "Pulsante", + "ControllerSettingsRStickUp": "Su", + "ControllerSettingsRStickDown": "Giù", + "ControllerSettingsRStickLeft": "Sinistra", + "ControllerSettingsRStickRight": "Destra", + "ControllerSettingsRStickStick": "Levetta", + "ControllerSettingsRStickInvertXAxis": "Inverti stick X", + "ControllerSettingsRStickInvertYAxis": "Inverti stick Y", + "ControllerSettingsRStickDeadzone": "Zona morta:", + "ControllerSettingsTriggersLeft": "Grilletto sinistro", + "ControllerSettingsTriggersRight": "Grilletto destro", + "ControllerSettingsTriggersButtonsLeft": "Pulsante dorsale sinistro", + "ControllerSettingsTriggersButtonsRight": "Pulsante dorsale destro", + "ControllerSettingsTriggers": "Grilletti", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Tasto sinitro", + "ControllerSettingsExtraButtonsRight": "Tasto destro", + "ControllerSettingsMisc": "Varie", + "ControllerSettingsTriggerThreshold": "Sensibilità dei grilletti:", + "ControllerSettingsMotion": "Movimento", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usa sensore compatibile con CemuHook", + "ControllerSettingsMotionControllerSlot": "Slot del controller:", + "ControllerSettingsMotionMirrorInput": "Input specchiato", + "ControllerSettingsMotionRightJoyConSlot": "Slot JoyCon destro:", + "ControllerSettingsMotionServerHost": "Server:", + "ControllerSettingsMotionGyroSensitivity": "Sensibilità del giroscopio:", + "ControllerSettingsMotionGyroDeadzone": "Zona morta del giroscopio:", + "ControllerSettingsSave": "Salva", + "ControllerSettingsClose": "Chiudi", + "UserProfilesSelectedUserProfile": "Profilo utente selezionato:", + "UserProfilesSaveProfileName": "Salva nome del profilo", + "UserProfilesChangeProfileImage": "Cambia immagine profilo", + "UserProfilesAvailableUserProfiles": "Profili utente disponibili:", + "UserProfilesAddNewProfile": "Aggiungi nuovo profilo", + "UserProfilesDeleteSelectedProfile": "Elimina il profilo selezionato", + "UserProfilesClose": "Chiudi", + "ProfileImageSelectionTitle": "Selezione dell'immagine profilo", + "ProfileImageSelectionHeader": "Scegli un'immagine profilo", + "ProfileImageSelectionNote": "Puoi importare un'immagine profilo personalizzata o selezionare un avatar dal firmware del sistema", + "ProfileImageSelectionImportImage": "Importa file immagine", + "ProfileImageSelectionSelectAvatar": "Seleziona avatar dal firmware", + "InputDialogTitle": "Finestra di dialogo ", + "InputDialogOk": "OK", + "InputDialogCancel": "Annulla", + "InputDialogAddNewProfileTitle": "Scegli il nome profilo", + "InputDialogAddNewProfileHeader": "Digita un nome profilo", + "InputDialogAddNewProfileSubtext": "(Lunghezza massima: {0})", + "AvatarChoose": "Scegli", + "AvatarSetBackgroundColor": "Imposta colore di sfondo", + "AvatarClose": "Chiudi", + "ControllerSettingsLoadProfileToolTip": "Carica profilo", + "ControllerSettingsAddProfileToolTip": "Aggiungi profilo", + "ControllerSettingsRemoveProfileToolTip": "Rimuovi profilo", + "ControllerSettingsSaveProfileToolTip": "Salva profilo", + "MenuBarFileToolsTakeScreenshot": "Fai uno screenshot", + "MenuBarFileToolsHideUi": "Nascondi UI", + "GameListContextMenuToggleFavorite": "Preferito", + "GameListContextMenuToggleFavoriteToolTip": "Segna il gioco come preferito", + "SettingsTabGeneralTheme": "Tema", + "SettingsTabGeneralThemeCustomTheme": "Percorso del tema personalizzato", + "SettingsTabGeneralThemeBaseStyle": "Modalità", + "SettingsTabGeneralThemeBaseStyleDark": "Scura", + "SettingsTabGeneralThemeBaseStyleLight": "Chiara", + "SettingsTabGeneralThemeEnableCustomTheme": "Attiva tema personalizzato", + "ButtonBrowse": "Sfoglia", + "ControllerSettingsConfigureGeneral": "Configura", + "ControllerSettingsRumble": "Vibrazione", + "ControllerSettingsRumbleStrongMultiplier": "Moltiplicatore vibrazione forte", + "ControllerSettingsRumbleWeakMultiplier": "Moltiplicatore vibrazione debole", + "DialogMessageSaveNotAvailableMessage": "Non ci sono dati di salvataggio per {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Vuoi creare dei dati di salvataggio per questo gioco?", + "DialogConfirmationTitle": "Ryujinx - Conferma", + "DialogUpdaterTitle": "Ryujinx - Aggiornamento", + "DialogErrorTitle": "Ryujinx - Errore", + "DialogWarningTitle": "Ryujinx - Avviso", + "DialogExitTitle": "Ryujinx - Esci", + "DialogErrorMessage": "Ryujinx ha riscontrato un problema", + "DialogExitMessage": "Sei sicuro di voler chiudere Ryujinx?", + "DialogExitSubMessage": "Tutti i dati non salvati andranno persi!", + "DialogMessageCreateSaveErrorMessage": "C'è stato un errore durante la creazione dei dati di salvataggio: {0}", + "DialogMessageFindSaveErrorMessage": "C'è stato un errore durante la ricerca dei dati di salvataggio: {0}", + "FolderDialogExtractTitle": "Scegli una cartella in cui estrarre", + "DialogNcaExtractionMessage": "Estrazione della sezione {0} da {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Estrattore sezione NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "L'estrazione è fallita. L'NCA principale non era presente nel file selezionato.", + "DialogNcaExtractionCheckLogErrorMessage": "L'estrazione è fallita. Leggi il log per più informazioni.", + "DialogNcaExtractionSuccessMessage": "Estrazione completata con successo.", + "DialogUpdaterConvertFailedMessage": "La conversione dell'attuale versione di Ryujinx è fallita.", + "DialogUpdaterCancelUpdateMessage": "Annullando l'aggiornamento!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Stai già usando la versione più recente di Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Si è verificato un errore durante il tentativo di ottenere informazioni sulla versione da GitHub Release. Ciò può essere causato se una nuova versione viene compilata da GitHub Actions. Riprova tra qualche minuto.", + "DialogUpdaterConvertFailedGithubMessage": "La conversione della versione di Ryujinx ricevuta da Github Release è fallita.", + "DialogUpdaterDownloadingMessage": "Download dell'aggiornamento...", + "DialogUpdaterExtractionMessage": "Estrazione dell'aggiornamento...", + "DialogUpdaterRenamingMessage": "Rinominazione dell'aggiornamento...", + "DialogUpdaterAddingFilesMessage": "Aggiunta del nuovo aggiornamento...", + "DialogUpdaterCompleteMessage": "Aggiornamento completato!", + "DialogUpdaterRestartMessage": "Vuoi riavviare Ryujinx adesso?", + "DialogUpdaterArchNotSupportedMessage": "Non stai usando un'architettura di sistema supportata!", + "DialogUpdaterArchNotSupportedSubMessage": "(Solo sistemi x64 sono supportati!)", + "DialogUpdaterNoInternetMessage": "Non sei connesso ad Internet!", + "DialogUpdaterNoInternetSubMessage": "Verifica di avere una connessione ad Internet funzionante!", + "DialogUpdaterDirtyBuildMessage": "Non puoi aggiornare una Dirty build di Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Scarica Ryujinx da https://ryujinx.org/ se stai cercando una versione supportata.", + "DialogRestartRequiredMessage": "Riavvio richiesto", + "DialogThemeRestartMessage": "Il tema è stato salvato. E' richiesto un riavvio per applicare un tema.", + "DialogThemeRestartSubMessage": "Vuoi riavviare?", + "DialogFirmwareInstallEmbeddedMessage": "Vuoi installare il firmware incorporato in questo gioco? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Non è stato trovato alcun firmware installato, ma Ryujinx è riuscito ad installare il firmware {0} dal gioco fornito.\nL'emulatore si avvierà adesso.", + "DialogFirmwareNoFirmwareInstalledMessage": "Nessun firmware installato", + "DialogFirmwareInstalledMessage": "Il firmware {0} è stato installato", + "DialogOpenSettingsWindowLabel": "Apri finestra delle impostazioni", + "DialogControllerAppletTitle": "Applet del controller", + "DialogMessageDialogErrorExceptionMessage": "Errore nella visualizzazione del Message Dialog: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Errore nella visualizzazione della tastiera software: {0}", + "DialogErrorAppletErrorExceptionMessage": "Errore nella visualizzazione dell'ErrorApplet Dialog: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nPer più informazioni su come risolvere questo errore, segui la nostra guida all'installazione.", + "DialogUserErrorDialogTitle": "Errore di Ryujinx ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Si è verificato un errore durante il recupero delle informazioni dall'API.", + "DialogAmiiboApiConnectErrorMessage": "Impossibile connettersi al server Amiibo API. Il servizio potrebbe essere fuori uso o potresti dover verificare che la tua connessione internet sia online.", + "DialogProfileInvalidProfileErrorMessage": "Il profilo {0} è incompatibile con l'attuale sistema di configurazione input.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Il profilo predefinito non può essere sovrascritto", + "DialogProfileDeleteProfileTitle": "Eliminazione profilo", + "DialogProfileDeleteProfileMessage": "Quest'azione è irreversibile, sei sicuro di voler continuare?", + "DialogWarning": "Avviso", + "DialogPPTCDeletionMessage": "Stai per eliminare la PPTC cache per :\n\n{0}\n\nSei sicuro di voler proseguire?", + "DialogPPTCDeletionErrorMessage": "Errore nell'eliminazione della PPTC cache a {0}: {1}", + "DialogShaderDeletionMessage": "Stai per eliminare la Shader cache per :\n\n{0}\n\nSei sicuro di voler proseguire?", + "DialogShaderDeletionErrorMessage": "Errore nell'eliminazione della Shader cache a {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx ha incontrato un errore", + "DialogInvalidTitleIdErrorMessage": "Errore UI: Il gioco selezionato non ha un ID titolo valido", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Un firmware del sistema valido non è stato trovato in {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Installa firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "La versione del sistema {0} sarà installata.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nQuesta sostituirà l'attuale versione di sistema {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nVuoi continuare?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Installazione del firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "La versione del sistema {0} è stata installata.", + "DialogUserProfileDeletionWarningMessage": "Non ci sarebbero altri profili da aprire se il profilo selezionato viene cancellato", + "DialogUserProfileDeletionConfirmMessage": "Vuoi eliminare il profilo selezionato?", + "DialogControllerSettingsModifiedConfirmMessage": "Le attuali impostazioni del controller sono state aggiornate.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Vuoi salvare?", + "DialogLoadNcaErrorMessage": "{0}. File errato: {1}", + "DialogDlcNoDlcErrorMessage": "Il file specificato non contiene un DLC per il titolo selezionato!", + "DialogPerformanceCheckLoggingEnabledMessage": "Hai abilitato il trace logging, che è progettato per essere usato solo dagli sviluppatori.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare il trace logging. Vuoi disabilitare il debug logging adesso?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Hai abilitato lo shader dumping, che è progettato per essere usato solo dagli sviluppatori.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Per prestazioni ottimali, si raccomanda di disabilitare lo shader dumping. Vuoi disabilitare lo shader dumping adesso?", + "DialogLoadAppGameAlreadyLoadedMessage": "Un gioco è già stato caricato", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Ferma l'emulazione o chiudi l'emulatore prima di avviare un altro gioco.", + "DialogUpdateAddUpdateErrorMessage": "Il file specificato non contiene un aggiornamento per il titolo selezionato!", + "DialogSettingsBackendThreadingWarningTitle": "Avviso - Backend Threading", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx deve essere riavviato dopo aver cambiato questa opzione per applicarla completamente. A seconda della tua piattaforma, potrebbe essere necessario disabilitare manualmente il multithreading del driver quando usi quello di Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Funzionalità", + "SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading", + "CommonAuto": "Auto", + "CommonOff": "Spento", + "CommonOn": "Acceso", + "InputDialogYes": "Si", + "InputDialogNo": "No", + "DialogProfileInvalidProfileNameErrorMessage": "Il nome del file contiene caratteri non validi. Riprova.", + "MenuBarOptionsPauseEmulation": "Pausa", + "MenuBarOptionsResumeEmulation": "Riprendi", + "AboutUrlTooltipMessage": "Clicca per aprire il sito web di Ryujinx nel tuo browser predefinito.", + "AboutDisclaimerMessage": "Ryujinx non è affiliato con Nintendo™,\no i suoi partner, in alcun modo.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) è usata\nnella nostra emulazione Amiibo.", + "AboutPatreonUrlTooltipMessage": "Clicca per aprire la pagina Patreon di Ryujinx nel tuo browser predefinito.", + "AboutGithubUrlTooltipMessage": "Clicca per aprire la pagina GitHub di Ryujinx nel tuo browser predefinito.", + "AboutDiscordUrlTooltipMessage": "Clicca per aprire un invito al server Discord di Ryujinx nel tuo browser predefinito.", + "AboutTwitterUrlTooltipMessage": "Clicca per aprire la pagina Twitter di Ryujinx nel tuo browser predefinito.", + "AboutRyujinxAboutTitle": "Informazioni:", + "AboutRyujinxAboutContent": "Ryujinx è un emulatore per la Nintendo Switch™.\nPer favore supportaci su Patreon.\nRicevi tutte le ultime notizie sul nostro Twitter o su Discord.\nGli sviluppatori interessati a contribuire possono trovare più informazioni sul nostro GitHub o Discord.", + "AboutRyujinxMaintainersTitle": "Mantenuto da:", + "AboutRyujinxMaintainersContentTooltipMessage": "Clicca per aprire la pagina dei Contributors nel tuo browser predefinito.", + "AboutRyujinxSupprtersTitle": "Supportato su Patreon da:", + "AmiiboSeriesLabel": "Serie Amiibo", + "AmiiboCharacterLabel": "Personaggio", + "AmiiboScanButtonLabel": "Scannerizza", + "AmiiboOptionsShowAllLabel": "Mostra tutti gli amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack: Usa un tag uuid casuale", + "DlcManagerTableHeadingEnabledLabel": "Abilitato", + "DlcManagerTableHeadingTitleIdLabel": "Titolo ID", + "DlcManagerTableHeadingContainerPathLabel": "Percorso del contenitore", + "DlcManagerTableHeadingFullPathLabel": "Percorso completo", + "DlcManagerRemoveAllButton": "Rimuovi tutti", + "DlcManagerEnableAllButton": "Abilita tutto", + "DlcManagerDisableAllButton": "Disabilita tutto", + "MenuBarOptionsChangeLanguage": "Cambia lingua", + "CommonSort": "Ordina", + "CommonShowNames": "Mostra nomi", + "CommonFavorite": "Preferito", + "OrderAscending": "Crescente", + "OrderDescending": "Decrescente", + "SettingsTabGraphicsFeatures": "Funzionalità & Miglioramenti", + "ErrorWindowTitle": "Finestra errore", + "ToggleDiscordTooltip": "Attiva o disattiva Discord Rich Presence", + "AddGameDirBoxTooltip": "Inserisci la directory di un gioco per aggiungerlo alla lista", + "AddGameDirTooltip": "Aggiungi la directory di un gioco alla lista", + "RemoveGameDirTooltip": "Rimuovi la directory di gioco selezionata", + "CustomThemeCheckTooltip": "Attiva o disattiva temi personalizzati nella GUI", + "CustomThemePathTooltip": "Percorso al tema GUI personalizzato", + "CustomThemeBrowseTooltip": "Sfoglia per cercare un tema GUI personalizzato", + "DockModeToggleTooltip": "Attiva o disabilta modalità TV", + "DirectKeyboardTooltip": "Attiva o disattiva \"il supporto all'accesso diretto alla tastiera (HID)\" (Fornisce l'accesso ai giochi alla tua tastiera come dispositivo di immissione del testo)", + "DirectMouseTooltip": "Attiva o disattiva \"il supporto all'accesso diretto al mouse (HID)\" (Fornisce l'accesso ai giochi al tuo mouse come dispositivo di puntamento)", + "RegionTooltip": "Cambia regione di sistema", + "LanguageTooltip": "Cambia lingua di sistema", + "TimezoneTooltip": "Cambia fuso orario di sistema", + "TimeTooltip": "Cambia data e ora di sistema", + "VSyncToggleTooltip": "Attiva o disattiva sincronizzazione verticale", + "PptcToggleTooltip": "Attiva o disattiva PPTC", + "FsIntegrityToggleTooltip": "Attiva controlli d'integrità sui file dei contenuti di gioco", + "AudioBackendTooltip": "Cambia backend audio", + "MemoryManagerTooltip": "Cambia il modo in cui la memoria guest è mappata e vi si accede. Influisce notevolmente sulle prestazioni della CPU emulata.", + "MemoryManagerSoftwareTooltip": "Usa una software page table per la traduzione degli indirizzi. Massima precisione ma prestazioni più lente.", + "MemoryManagerHostTooltip": "Mappa direttamente la memoria nello spazio degli indirizzi dell'host. Compilazione ed esecuzione JIT molto più veloce.", + "MemoryManagerUnsafeTooltip": "Mappa direttamente la memoria, ma non maschera l'indirizzo all'interno dello spazio degli indirizzi guest prima dell'accesso. Più veloce, ma a costo della sicurezza. L'applicazione guest può accedere alla memoria da qualsiasi punto di Ryujinx, quindi esegui solo programmi di cui ti fidi con questa modalità.", + "DRamTooltip": "Espande l'ammontare di memoria sul sistema emulato da 4GiB A 6GiB", + "IgnoreMissingServicesTooltip": "Attiva o disattiva l'opzione di ignorare i servizi mancanti", + "GraphicsBackendThreadingTooltip": "Attiva il Graphics Backend Multithreading", + "GalThreadingTooltip": "Esegue i comandi del backend grafico su un secondo thread. Permette il multithreading runtime della compilazione degli shader, riduce lo stuttering e migliora le prestazioni sui driver senza supporto multithreading proprio. Varia leggermente le prestazioni di picco sui driver con multithreading. Ryujinx potrebbe aver bisogno di essere riavviato per disabilitare correttamente il multithreading integrato nel driver, o potrebbe essere necessario farlo manualmente per ottenere le migliori prestazioni.", + "ShaderCacheToggleTooltip": "Attiva o disattiva la Shader Cache", + "ResolutionScaleTooltip": "Scala della risoluzione applicata ai render targets applicabili", + "ResolutionScaleEntryTooltip": "Scala della risoluzione in virgola mobile, come 1,5. Le scale non integrali hanno maggiori probabilità di causare problemi o crash.", + "AnisotropyTooltip": "Livello del filtro anisotropico (imposta su Auto per usare il valore richiesto dal gioco)", + "AspectRatioTooltip": "Rapporto d'aspetto applicato alla finestra del renderer.", + "ShaderDumpPathTooltip": "Percorso di dump Graphics Shaders", + "FileLogTooltip": "Attiva o disattiva il logging su file", + "StubLogTooltip": "Attiva messaggi stub log", + "InfoLogTooltip": "Attiva messaggi info log", + "WarnLogTooltip": "Attiva messaggi warning log", + "ErrorLogTooltip": "Attiva messaggi error log", + "TraceLogTooltip": "Attiva messaggi trace log", + "GuestLogTooltip": "Attiva messaggi guest log", + "FileAccessLogTooltip": "Attiva messaggi file access log", + "FSAccessLogModeTooltip": "Attiva output FS access log alla console. Le modalità possibili sono 0-3", + "DeveloperOptionTooltip": "Usa con attenzione", + "OpenGlLogLevel": "Richiede livelli di log appropriati abilitati", + "DebugLogTooltip": "Attiva messaggi debug log", + "LoadApplicationFileTooltip": "Apri un file explorer per scegliere un file compatibile Switch da caricare", + "LoadApplicationFolderTooltip": "Apri un file explorer per scegliere un file compatibile Switch, applicazione sfusa da caricare", + "OpenRyujinxFolderTooltip": "Apri la cartella del filesystem di Ryujinx", + "OpenRyujinxLogsTooltip": "Apre la cartella dove vengono scritti i log", + "ExitTooltip": "Esci da Ryujinx", + "OpenSettingsTooltip": "Apri finestra delle impostazioni", + "OpenProfileManagerTooltip": "Apri la finestra di gestione dei profili utente", + "StopEmulationTooltip": "Ferma l'emulazione del gioco attuale e torna alla selezione dei giochi", + "CheckUpdatesTooltip": "Controlla la presenza di aggiornamenti di Ryujinx", + "OpenAboutTooltip": "Apri finestra delle informazioni", + "GridSize": "Dimensione griglia", + "GridSizeTooltip": "Cambia la dimensione dei riquardi della griglia", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portoghese Brasiliano", + "AboutRyujinxContributorsButtonHeader": "Vedi tutti i Contributors", + "SettingsTabSystemAudioVolume": "Volume: ", + "AudioVolumeTooltip": "Cambia volume audio", + "SettingsTabSystemEnableInternetAccess": "Attiva Guest Internet Access", + "EnableInternetAccessTooltip": "Attiva il guest Internet access. Se abilitato, l'applicazione si comporterà come se la console Switch emulata fosse collegata a Internet. Si noti che in alcuni casi, le applicazioni possono comunque accedere a Internet anche con questa opzione disabilitata", + "GameListContextMenuManageCheatToolTip": "Gestisci Cheats", + "GameListContextMenuManageCheat": "Gestisci Cheats", + "ControllerSettingsStickRange": "Raggio:", + "DialogStopEmulationTitle": "Ryujinx - Ferma emulazione", + "DialogStopEmulationMessage": "Sei sicuro di voler fermare l'emulazione?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Audio", + "SettingsTabNetwork": "Rete", + "SettingsTabNetworkConnection": "Connessione di rete", + "SettingsTabCpuCache": "Cache CPU", + "SettingsTabCpuMemory": "Memoria CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Per favore aggiorna Ryujinx via FlatHub.", + "UpdaterDisabledWarningTitle": "Updater disabilitato!", + "GameListContextMenuOpenSdModsDirectory": "Apri cartella delle mods Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Apre la cartella Atmosphere della scheda SD alternativa che contiene le Mod dell'applicazione. Utile per mod confezionate per hardware reale", + "ControllerSettingsRotate90": "Ruota in senso orario di 90°", + "IconSize": "Dimensioni icona", + "IconSizeTooltip": "Cambia le dimensioni dell'icona di un gioco", + "MenuBarOptionsShowConsole": "Mostra console", + "ShaderCachePurgeError": "Errore nella pulizia della shader cache a {0}: {1}", + "UserErrorNoKeys": "Chiavi non trovate", + "UserErrorNoFirmware": "Firmware non trovato", + "UserErrorFirmwareParsingFailed": "Errori di analisi del firmware", + "UserErrorApplicationNotFound": "Applicazione non trovata", + "UserErrorUnknown": "Errore sconosciuto", + "UserErrorUndefined": "Errore non definito", + "UserErrorNoKeysDescription": "Ryujinx non è riuscito a trovare il file 'prod.keys'", + "UserErrorNoFirmwareDescription": "Ryujinx non è riuscito a trovare alcun firmware installato", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx non è riuscito ad analizzare il firmware. Questo di solito è causato da chiavi non aggiornate.", + "UserErrorApplicationNotFoundDescription": "Ryujinx non è riuscito a trovare un'applicazione valida nel percorso specificato.", + "UserErrorUnknownDescription": "Si è verificato un errore sconosciuto!", + "UserErrorUndefinedDescription": "Si è verificato un errore sconosciuto! Non dovrebbe succedere, per favore contatta uno sviluppatore!", + "OpenSetupGuideMessage": "Apri la guida all'installazione", + "NoUpdate": "Nessun aggiornamento", + "TitleUpdateVersionLabel": "Versione {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Conferma", + "FileDialogAllTypes": "Tutti i tipi", + "Never": "Mai", + "SwkbdMinCharacters": "Non può avere meno di {0} caratteri", + "SwkbdMinRangeCharacters": "Può avere da {0} a {1} caratteri", + "SoftwareKeyboard": "Tastiera software", + "DialogControllerAppletMessagePlayerRange": "L'applicazione richiede {0} giocatori con:\n\nTIPI: {1}\n\nGIOCATORI: {2}\n\n{3}Apri le impostazioni e riconfigura l'input adesso o premi Chiudi.", + "DialogControllerAppletMessage": "L'applicazione richiede esattamente {0} giocatori con:\n\nTIPI: {1}\n\nGIOCATORI: {2}\n\n{3}Apri le impostazioni e riconfigura l'input adesso o premi Chiudi.", + "DialogControllerAppletDockModeSet": "Modalità TV attivata. Neanche portatile è valida.\n\n", + "UpdaterRenaming": "Rinominazione dei vecchi files...", + "UpdaterRenameFailed": "L'updater non è riuscito a rinominare il file: {0}", + "UpdaterAddingFiles": "Aggiunta nuovi files...", + "UpdaterExtracting": "Estrazione aggiornamento...", + "UpdaterDownloading": "Download aggiornamento...", + "Game": "Gioco", + "Docked": "TV", + "Handheld": "Portatile", + "ConnectionError": "Errore di connessione.", + "AboutPageDeveloperListMore": "{0} e altri ancora...", + "ApiError": "Errore dell'API.", + "LoadingHeading": "Caricamento di {0}", + "CompilingPPTC": "Compilazione PTC", + "CompilingShaders": "Compilazione Shaders", + "AllKeyboards": "Tutte le tastiere", + "OpenFileDialogTitle": "Seleziona un file supportato da aprire", + "OpenFolderDialogTitle": "Seleziona una cartella con un gioco estratto", + "AllSupportedFormats": "Tutti i formati supportati", + "RyujinxUpdater": "Aggiornamento Ryujinx", + "SettingsTabHotkeys": "Tasti di scelta rapida", + "SettingsTabHotkeysHotkeys": "Tasti di scelta rapida", + "SettingsTabHotkeysToggleVsyncHotkey": "VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Cattura Schermo:", + "SettingsTabHotkeysShowUiHotkey": "Mostra UI:", + "SettingsTabHotkeysPauseHotkey": "Metti in pausa:", + "SettingsTabHotkeysToggleMuteHotkey": "Muta:", + "ControllerMotionTitle": "Impostazioni dei sensori di movimento", + "ControllerRumbleTitle": "Impostazioni di vibrazione", + "SettingsSelectThemeFileDialogTitle": "Seleziona file del tema", + "SettingsXamlThemeFile": "File del tema xaml", + "AvatarWindowTitle": "Gestisci account - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Sconosciuto", + "Usage": "Utilizzo", + "Writable": "Scrivibile", + "SelectDlcDialogTitle": "Seleziona file dei DLC", + "SelectUpdateDialogTitle": "Seleziona file di aggiornamento", + "UserProfileWindowTitle": "Gestisci profili degli utenti", + "CheatWindowTitle": "Gestisci cheat dei giochi", + "DlcWindowTitle": "Gestisci DLC dei giochi", + "UpdateWindowTitle": "Gestisci aggiornamenti dei giochi", + "CheatWindowHeading": "Cheat disponibiili per {0} [{1}]", + "DlcWindowHeading": "DLC disponibili per {0} [{1}]", + "UserProfilesEditProfile": "Modifica selezionati", + "Cancel": "Annulla", + "Save": "Salva", + "Discard": "Scarta", + "UserProfilesSetProfileImage": "Imposta immagine profilo", + "UserProfileEmptyNameError": "È richiesto un nome", + "UserProfileNoImageError": "Dev'essere impostata un'immagine profilo", + "GameUpdateWindowHeading": "Aggiornamenti disponibili per {0} [{1}]", + "SettingsTabHotkeysResScaleUpHotkey": "Aumentare la risoluzione:", + "SettingsTabHotkeysResScaleDownHotkey": "Diminuire la risoluzione:", + "UserProfilesName": "Nome:", + "UserProfilesUserId": "ID utente:", + "SettingsTabGraphicsBackend": "Backend grafica", + "SettingsTabGraphicsBackendTooltip": "Backend grafica da usare", + "SettingsEnableTextureRecompression": "Abilita Ricompressione Texture", + "SettingsEnableTextureRecompressionTooltip": "Comprime alcune texture per ridurre l'utilizzo della VRAM.\n\nL'utilizzo è consigliato con GPU con meno di 4GB di VRAM.\n\nLascia su OFF se non sei sicuro.", + "SettingsTabGraphicsPreferredGpu": "GPU preferita", + "SettingsTabGraphicsPreferredGpuTooltip": "Seleziona la scheda grafica che verrà usata con la backend grafica Vulkan.\n\nNon influenza la GPU che userà OpenGL.\n\nImposta la GPU contrassegnata come \"dGPU\" se non sei sicuro. Se non ce n'è una, lascia intatta quest'impostazione.", + "SettingsAppRequiredRestartMessage": "È richiesto un riavvio di Ryujinx", + "SettingsGpuBackendRestartMessage": "Le impostazioni della backend grafica o della GPU sono state modificate. Questo richiederà un riavvio perché le modifiche siano applicate", + "SettingsGpuBackendRestartSubMessage": "Vuoi riavviare ora?", + "RyujinxUpdaterMessage": "Vuoi aggiornare Ryujinx all'ultima versione?", + "SettingsTabHotkeysVolumeUpHotkey": "Aumentare il volume:", + "SettingsTabHotkeysVolumeDownHotkey": "Diminuire il volume:", + "SettingsEnableMacroHLE": "Abilita Macro HLE", + "SettingsEnableMacroHLETooltip": "Emulazione di alto livello del codice Macro GPU.\n\nMigliora le prestazioni, ma può causare anomalie grafiche in alcuni giochi.\n\nLasciare ON se non sei sicuro.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Gestisci i salvataggi", + "DeleteUserSave": "Vuoi eliminare il salvataggio utente per questo gioco?", + "IrreversibleActionNote": "Questa azione non è reversibile.", + "SaveManagerHeading": "Gestisci i salvataggi per {0}", + "SaveManagerTitle": "Gestione Salvataggi", + "Name": "Nome", + "Size": "Dimensione", + "Search": "Cerca", + "UserProfilesRecoverLostAccounts": "Recupera il tuo account", + "Recover": "Recupera", + "UserProfilesRecoverHeading": "Sono stati trovati dei salvataggi per i seguenti account" +} diff --git a/src/Ryujinx.Ava/Assets/Locales/ja_JP.json b/src/Ryujinx.Ava/Assets/Locales/ja_JP.json new file mode 100644 index 00000000..581443f5 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/ja_JP.json @@ -0,0 +1,614 @@ +{ + "Language": "日本語", + "MenuBarFileOpenApplet": "アプレットを開く", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "スタンドアロンモードで Mii エディタアプレットを開きます", + "SettingsTabInputDirectMouseAccess": "マウス直接アクセス", + "SettingsTabSystemMemoryManagerMode": "メモリ管理モード:", + "SettingsTabSystemMemoryManagerModeSoftware": "ソフトウェア", + "SettingsTabSystemMemoryManagerModeHost": "ホスト (高速)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "ホスト, チェックなし (最高速, 安全でない)", + "MenuBarFile": "ファイル(_F)", + "MenuBarFileOpenFromFile": "ファイルからアプリケーションをロード(_L)", + "MenuBarFileOpenUnpacked": "展開されたゲームをロード", + "MenuBarFileOpenEmuFolder": "Ryujinx フォルダを開く", + "MenuBarFileOpenLogsFolder": "ログフォルダを開く", + "MenuBarFileExit": "終了(_E)", + "MenuBarOptions": "オプション", + "MenuBarOptionsToggleFullscreen": "全画面切り替え", + "MenuBarOptionsStartGamesInFullscreen": "全画面モードでゲームを開始", + "MenuBarOptionsStopEmulation": "エミュレーションを停止", + "MenuBarOptionsSettings": "設定(_S)", + "MenuBarOptionsManageUserProfiles": "ユーザプロファイルを管理(_M)", + "MenuBarActions": "アクション(_A)", + "MenuBarOptionsSimulateWakeUpMessage": "スリープ復帰メッセージをシミュレート", + "MenuBarActionsScanAmiibo": "Amiibo をスキャン", + "MenuBarTools": "ツール(_T)", + "MenuBarToolsInstallFirmware": "ファームウェアをインストール", + "MenuBarFileToolsInstallFirmwareFromFile": "XCI または ZIP からファームウェアをインストール", + "MenuBarFileToolsInstallFirmwareFromDirectory": "ディレクトリからファームウェアをインストール", + "MenuBarHelp": "ヘルプ", + "MenuBarHelpCheckForUpdates": "アップデートを確認", + "MenuBarHelpAbout": "Ryujinx について", + "MenuSearch": "検索...", + "GameListHeaderFavorite": "お気に入り", + "GameListHeaderIcon": "アイコン", + "GameListHeaderApplication": "名称", + "GameListHeaderDeveloper": "開発元", + "GameListHeaderVersion": "バージョン", + "GameListHeaderTimePlayed": "プレイ時間", + "GameListHeaderLastPlayed": "最終プレイ日時", + "GameListHeaderFileExtension": "ファイル拡張子", + "GameListHeaderFileSize": "ファイルサイズ", + "GameListHeaderPath": "パス", + "GameListContextMenuOpenUserSaveDirectory": "セーブディレクトリを開く", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "アプリケーションのユーザセーブデータを格納するディレクトリを開きます", + "GameListContextMenuOpenDeviceSaveDirectory": "デバイスディレクトリを開く", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "アプリケーションのデバイスセーブデータを格納するディレクトリを開きます", + "GameListContextMenuOpenBcatSaveDirectory": "BCATディレクトリを開く", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "アプリケーションの BCAT セーブデータを格納するディレクトリを開きます", + "GameListContextMenuManageTitleUpdates": "アップデートを管理", + "GameListContextMenuManageTitleUpdatesToolTip": "タイトルのアップデート管理ウインドウを開きます", + "GameListContextMenuManageDlc": "DLCを管理", + "GameListContextMenuManageDlcToolTip": "DLC管理ウインドウを開きます", + "GameListContextMenuOpenModsDirectory": "Modディレクトリを開く", + "GameListContextMenuOpenModsDirectoryToolTip": "アプリケーションの Mod データを格納するディレクトリを開きます", + "GameListContextMenuCacheManagement": "キャッシュ管理", + "GameListContextMenuCacheManagementPurgePptc": "PPTC を再構築", + "GameListContextMenuCacheManagementPurgePptcToolTip": "次回のゲーム起動時に PPTC を再構築します", + "GameListContextMenuCacheManagementPurgeShaderCache": "シェーダキャッシュを破棄", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "アプリケーションのシェーダキャッシュを破棄します", + "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC ディレクトリを開く", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "アプリケーションの PPTC キャッシュを格納するディレクトリを開きます", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "シェーダキャッシュディレクトリを開く", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "アプリケーションのシェーダキャッシュを格納するディレクトリを開きます", + "GameListContextMenuExtractData": "データを展開", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "現在のアプリケーション設定(アップデート含む)から ExeFS セクションを展開します", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "現在のアプリケーション設定(アップデート含む)から RomFS セクションを展開します", + "GameListContextMenuExtractDataLogo": "ロゴ", + "GameListContextMenuExtractDataLogoToolTip": "現在のアプリケーション設定(アップデート含む)からロゴセクションを展開します", + "StatusBarGamesLoaded": "{0}/{1} ゲーム", + "StatusBarSystemVersion": "システムバージョン: {0}", + "Settings": "設定", + "SettingsTabGeneral": "ユーザインタフェース", + "SettingsTabGeneralGeneral": "一般", + "SettingsTabGeneralEnableDiscordRichPresence": "Discord リッチプレゼンスを有効", + "SettingsTabGeneralCheckUpdatesOnLaunch": "起動時にアップデートを確認", + "SettingsTabGeneralShowConfirmExitDialog": "\"終了を確認\" ダイアログを表示", + "SettingsTabGeneralHideCursorOnIdle": "アイドル時にカーソルを隠す", + "SettingsTabGeneralGameDirectories": "ゲームディレクトリ", + "SettingsTabGeneralAdd": "追加", + "SettingsTabGeneralRemove": "削除", + "SettingsTabSystem": "システム", + "SettingsTabSystemCore": "コア", + "SettingsTabSystemSystemRegion": "地域:", + "SettingsTabSystemSystemRegionJapan": "日本", + "SettingsTabSystemSystemRegionUSA": "アメリカ", + "SettingsTabSystemSystemRegionEurope": "ヨーロッパ", + "SettingsTabSystemSystemRegionAustralia": "オーストラリア", + "SettingsTabSystemSystemRegionChina": "中国", + "SettingsTabSystemSystemRegionKorea": "韓国", + "SettingsTabSystemSystemRegionTaiwan": "台湾", + "SettingsTabSystemSystemLanguage": "言語:", + "SettingsTabSystemSystemLanguageJapanese": "日本語", + "SettingsTabSystemSystemLanguageAmericanEnglish": "英語(アメリカ)", + "SettingsTabSystemSystemLanguageFrench": "フランス語", + "SettingsTabSystemSystemLanguageGerman": "ドイツ語", + "SettingsTabSystemSystemLanguageItalian": "イタリア語", + "SettingsTabSystemSystemLanguageSpanish": "スペイン語", + "SettingsTabSystemSystemLanguageChinese": "中国語", + "SettingsTabSystemSystemLanguageKorean": "韓国語", + "SettingsTabSystemSystemLanguageDutch": "オランダ語", + "SettingsTabSystemSystemLanguagePortuguese": "ポルトガル語", + "SettingsTabSystemSystemLanguageRussian": "ロシア語", + "SettingsTabSystemSystemLanguageTaiwanese": "台湾語", + "SettingsTabSystemSystemLanguageBritishEnglish": "英語(イギリス)", + "SettingsTabSystemSystemLanguageCanadianFrench": "フランス語(カナダ)", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "スペイン語(ラテンアメリカ)", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "中国語", + "SettingsTabSystemSystemLanguageTraditionalChinese": "台湾語", + "SettingsTabSystemSystemTimeZone": "タイムゾーン:", + "SettingsTabSystemSystemTime": "時刻:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "ファイルシステム整合性チェック", + "SettingsTabSystemAudioBackend": "音声バックエンド:", + "SettingsTabSystemAudioBackendDummy": "ダミー", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "ハック", + "SettingsTabSystemHacksNote": " (挙動が不安定になる可能性があります)", + "SettingsTabSystemExpandDramSize": "DRAMサイズを6GiBに拡大", + "SettingsTabSystemIgnoreMissingServices": "未実装サービスを無視", + "SettingsTabGraphics": "グラフィックス", + "SettingsTabGraphicsAPI": "グラフィックスAPI", + "SettingsTabGraphicsEnableShaderCache": "シェーダキャッシュを有効", + "SettingsTabGraphicsAnisotropicFiltering": "異方性フィルタリング:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "自動", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "解像度:", + "SettingsTabGraphicsResolutionScaleCustom": "カスタム (非推奨)", + "SettingsTabGraphicsResolutionScaleNative": "ネイティブ (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "アスペクト比:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "ウインドウサイズに合わせる", + "SettingsTabGraphicsDeveloperOptions": "開発者向けオプション", + "SettingsTabGraphicsShaderDumpPath": "グラフィックス シェーダダンプパス:", + "SettingsTabLogging": "ロギング", + "SettingsTabLoggingLogging": "ロギング", + "SettingsTabLoggingEnableLoggingToFile": "ファイルへのロギングを有効", + "SettingsTabLoggingEnableStubLogs": "Stub ログを有効", + "SettingsTabLoggingEnableInfoLogs": "Info ログを有効", + "SettingsTabLoggingEnableWarningLogs": "Warning ログを有効", + "SettingsTabLoggingEnableErrorLogs": "Error ログを有効", + "SettingsTabLoggingEnableTraceLogs": "Trace ログを有効", + "SettingsTabLoggingEnableGuestLogs": "Guest ログを有効", + "SettingsTabLoggingEnableFsAccessLogs": "Fs アクセスログを有効", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs グローバルアクセスログモード:", + "SettingsTabLoggingDeveloperOptions": "開発者オプション (警告: パフォーマンスが低下します)", + "SettingsTabLoggingGraphicsBackendLogLevel": "グラフィックスバックエンド ログレベル:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "なし", + "SettingsTabLoggingGraphicsBackendLogLevelError": "エラー", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "パフォーマンス低下", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "すべて", + "SettingsTabLoggingEnableDebugLogs": "デバッグログを有効", + "SettingsTabInput": "入力", + "SettingsTabInputEnableDockedMode": "ドッキングモード", + "SettingsTabInputDirectKeyboardAccess": "キーボード直接アクセス", + "SettingsButtonSave": "セーブ", + "SettingsButtonClose": "閉じる", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "キャンセル", + "SettingsButtonApply": "適用", + "ControllerSettingsPlayer": "プレイヤー", + "ControllerSettingsPlayer1": "プレイヤー 1", + "ControllerSettingsPlayer2": "プレイヤー 2", + "ControllerSettingsPlayer3": "プレイヤー 3", + "ControllerSettingsPlayer4": "プレイヤー 4", + "ControllerSettingsPlayer5": "プレイヤー 5", + "ControllerSettingsPlayer6": "プレイヤー 6", + "ControllerSettingsPlayer7": "プレイヤー 7", + "ControllerSettingsPlayer8": "プレイヤー 8", + "ControllerSettingsHandheld": "携帯", + "ControllerSettingsInputDevice": "入力デバイス", + "ControllerSettingsRefresh": "更新", + "ControllerSettingsDeviceDisabled": "無効", + "ControllerSettingsControllerType": "コントローラ種別", + "ControllerSettingsControllerTypeHandheld": "携帯", + "ControllerSettingsControllerTypeProController": "Pro コントローラ", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon ペア", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon 左", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon 右", + "ControllerSettingsProfile": "プロファイル", + "ControllerSettingsProfileDefault": "デフォルト", + "ControllerSettingsLoad": "ロード", + "ControllerSettingsAdd": "追加", + "ControllerSettingsRemove": "削除", + "ControllerSettingsButtons": "ボタン", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "十字キー", + "ControllerSettingsDPadUp": "上", + "ControllerSettingsDPadDown": "下", + "ControllerSettingsDPadLeft": "左", + "ControllerSettingsDPadRight": "右", + "ControllerSettingsLStick": "左スティック", + "ControllerSettingsLStickButton": "ボタン", + "ControllerSettingsLStickUp": "上", + "ControllerSettingsLStickDown": "下", + "ControllerSettingsLStickLeft": "左", + "ControllerSettingsLStickRight": "右", + "ControllerSettingsLStickStick": "スティック", + "ControllerSettingsLStickInvertXAxis": "X軸を反転", + "ControllerSettingsLStickInvertYAxis": "Y軸を反転", + "ControllerSettingsLStickDeadzone": "遊び:", + "ControllerSettingsRStick": "右スティック", + "ControllerSettingsRStickButton": "ボタン", + "ControllerSettingsRStickUp": "上", + "ControllerSettingsRStickDown": "下", + "ControllerSettingsRStickLeft": "左", + "ControllerSettingsRStickRight": "右", + "ControllerSettingsRStickStick": "スティック", + "ControllerSettingsRStickInvertXAxis": "X軸を反転", + "ControllerSettingsRStickInvertYAxis": "Y軸を反転", + "ControllerSettingsRStickDeadzone": "遊び:", + "ControllerSettingsTriggersLeft": "左トリガー", + "ControllerSettingsTriggersRight": "右トリガー", + "ControllerSettingsTriggersButtonsLeft": "左トリガーボタン", + "ControllerSettingsTriggersButtonsRight": "右トリガーボタン", + "ControllerSettingsTriggers": "トリガー", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "左ボタン", + "ControllerSettingsExtraButtonsRight": "右ボタン", + "ControllerSettingsMisc": "その他", + "ControllerSettingsTriggerThreshold": "トリガーしきい値:", + "ControllerSettingsMotion": "モーション", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook 互換モーションを使用", + "ControllerSettingsMotionControllerSlot": "コントローラ スロット:", + "ControllerSettingsMotionMirrorInput": "入力反転", + "ControllerSettingsMotionRightJoyConSlot": "JoyCon 右 スロット:", + "ControllerSettingsMotionServerHost": "サーバ:", + "ControllerSettingsMotionGyroSensitivity": "ジャイロ感度:", + "ControllerSettingsMotionGyroDeadzone": "ジャイロ遊び:", + "ControllerSettingsSave": "セーブ", + "ControllerSettingsClose": "閉じる", + "UserProfilesSelectedUserProfile": "選択されたユーザプロファイル:", + "UserProfilesSaveProfileName": "プロファイル名をセーブ", + "UserProfilesChangeProfileImage": "プロファイル画像を変更", + "UserProfilesAvailableUserProfiles": "利用可能なユーザプロファイル:", + "UserProfilesAddNewProfile": "プロファイルを作成", + "UserProfilesDelete": "削除", + "UserProfilesClose": "閉じる", + "ProfileImageSelectionTitle": "プロファイル画像選択", + "ProfileImageSelectionHeader": "プロファイル画像を選択", + "ProfileImageSelectionNote": "カスタム画像をインポート, またはファームウェア内のアバターを選択できます", + "ProfileImageSelectionImportImage": "画像ファイルをインポート", + "ProfileImageSelectionSelectAvatar": "ファームウェア内のアバターを選択", + "InputDialogTitle": "入力ダイアログ", + "InputDialogOk": "OK", + "InputDialogCancel": "キャンセル", + "InputDialogAddNewProfileTitle": "プロファイル名を選択", + "InputDialogAddNewProfileHeader": "プロファイル名を入力してください", + "InputDialogAddNewProfileSubtext": "(最大長: {0})", + "AvatarChoose": "選択", + "AvatarSetBackgroundColor": "背景色を指定", + "AvatarClose": "閉じる", + "ControllerSettingsLoadProfileToolTip": "プロファイルをロード", + "ControllerSettingsAddProfileToolTip": "プロファイルを追加", + "ControllerSettingsRemoveProfileToolTip": "プロファイルを削除", + "ControllerSettingsSaveProfileToolTip": "プロファイルをセーブ", + "MenuBarFileToolsTakeScreenshot": "スクリーンショットを撮影", + "MenuBarFileToolsHideUi": "Hide UI", + "GameListContextMenuToggleFavorite": "お気に入りを切り替え", + "GameListContextMenuToggleFavoriteToolTip": "ゲームをお気に入りに含めるかどうかを切り替えます", + "SettingsTabGeneralTheme": "テーマ", + "SettingsTabGeneralThemeCustomTheme": "カスタムテーマパス", + "SettingsTabGeneralThemeBaseStyle": "基本スタイル", + "SettingsTabGeneralThemeBaseStyleDark": "ダーク", + "SettingsTabGeneralThemeBaseStyleLight": "ライト", + "SettingsTabGeneralThemeEnableCustomTheme": "カスタムテーマを有効", + "ButtonBrowse": "参照", + "ControllerSettingsConfigureGeneral": "設定", + "ControllerSettingsRumble": "振動", + "ControllerSettingsRumbleStrongMultiplier": "強振動の補正値", + "ControllerSettingsRumbleWeakMultiplier": "弱振動の補正値", + "DialogMessageSaveNotAvailableMessage": "{0} [{1:x16}] のセーブデータはありません", + "DialogMessageSaveNotAvailableCreateSaveMessage": "このゲームのセーブデータを作成してよろしいですか?", + "DialogConfirmationTitle": "Ryujinx - 確認", + "DialogUpdaterTitle": "Ryujinx - アップデータ", + "DialogErrorTitle": "Ryujinx - エラー", + "DialogWarningTitle": "Ryujinx - 警告", + "DialogExitTitle": "Ryujinx - 終了", + "DialogErrorMessage": "エラーが発生しました", + "DialogExitMessage": "Ryujinx を閉じてよろしいですか?", + "DialogExitSubMessage": "セーブされていないデータはすべて失われます!", + "DialogMessageCreateSaveErrorMessage": "セーブデータ: {0} の作成中にエラーが発生しました", + "DialogMessageFindSaveErrorMessage": "セーブデータ: {0} の検索中にエラーが発生しました", + "FolderDialogExtractTitle": "展開フォルダを選択", + "DialogNcaExtractionMessage": "{1} から {0} セクションを展開中...", + "DialogNcaExtractionTitle": "Ryujinx - NCA セクション展開", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "展開に失敗しました. 選択されたファイルにはメイン NCA が存在しません.", + "DialogNcaExtractionCheckLogErrorMessage": "展開に失敗しました. 詳細はログを確認してください.", + "DialogNcaExtractionSuccessMessage": "展開が正常終了しました", + "DialogUpdaterConvertFailedMessage": "現在の Ryujinx バージョンの変換に失敗しました.", + "DialogUpdaterCancelUpdateMessage": "アップデータをキャンセル中!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "最新バージョンの Ryujinx を使用中です!", + "DialogUpdaterFailedToGetVersionMessage": "An error has occurred when trying to get release information from GitHub Release. This can be caused if a new release is being compiled by GitHub Actions. Try again in a few minutes.", + "DialogUpdaterConvertFailedGithubMessage": "Github から取得した Ryujinx バージョンの変換に失敗しました.", + "DialogUpdaterDownloadingMessage": "アップデートをダウンロード中...", + "DialogUpdaterExtractionMessage": "アップデートを展開中...", + "DialogUpdaterRenamingMessage": "アップデートをリネーム中...", + "DialogUpdaterAddingFilesMessage": "新規アップデートを追加中...", + "DialogUpdaterCompleteMessage": "アップデート完了!", + "DialogUpdaterRestartMessage": "すぐに Ryujinx を再起動しますか?", + "DialogUpdaterArchNotSupportedMessage": "サポート外のアーキテクチャです!", + "DialogUpdaterArchNotSupportedSubMessage": "(x64 システムのみサポートしています!)", + "DialogUpdaterNoInternetMessage": "インターネットに接続されていません!", + "DialogUpdaterNoInternetSubMessage": "インターネット接続が正常動作しているか確認してください!", + "DialogUpdaterDirtyBuildMessage": "Dirty ビルドの Ryujinx はアップデートできません!", + "DialogUpdaterDirtyBuildSubMessage": "サポートされているバージョンをお探しなら, https://ryujinx.org/ で Ryujinx をダウンロードしてください.", + "DialogRestartRequiredMessage": "再起動が必要", + "DialogThemeRestartMessage": "テーマがセーブされました. テーマを適用するには再起動が必要です.", + "DialogThemeRestartSubMessage": "再起動しますか", + "DialogFirmwareInstallEmbeddedMessage": "このゲームに含まれるファームウェアをインストールしてよろしいですか? (ファームウェア {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "ファームウェアがインストールされていませんが, ゲームに含まれるファームウェア {0} をインストールできます.\\nエミュレータが開始します.", + "DialogFirmwareNoFirmwareInstalledMessage": "ファームウェアがインストールされていません", + "DialogFirmwareInstalledMessage": "ファームウェア {0} がインストールされました", + "DialogOpenSettingsWindowLabel": "設定ウインドウを開く", + "DialogControllerAppletTitle": "コントローラアプレット", + "DialogMessageDialogErrorExceptionMessage": "メッセージダイアログ表示エラー: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "ソフトウェアキーボード表示エラー: {0}", + "DialogErrorAppletErrorExceptionMessage": "エラーアプレットダイアログ表示エラー: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nこのエラーへの対処方法については, セットアップガイドを参照してください.", + "DialogUserErrorDialogTitle": "Ryujinx エラー ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "API からの情報取得中にエラーが発生しました.", + "DialogAmiiboApiConnectErrorMessage": "Amiibo API サーバに接続できませんでした. サーバがダウンしているか, インターネット接続に問題があるかもしれません.", + "DialogProfileInvalidProfileErrorMessage": "プロファイル {0} は現在の入力設定システムと互換性がありません.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "デフォルトのプロファイルは上書きできません", + "DialogProfileDeleteProfileTitle": "プロファイルを削除中", + "DialogProfileDeleteProfileMessage": "このアクションは元に戻せません. 本当に続けてよろしいですか?", + "DialogWarning": "警告", + "DialogPPTCDeletionMessage": "次回起動時に PPTC を再構築します:\n\n{0}\n\n実行してよろしいですか?", + "DialogPPTCDeletionErrorMessage": "PPTC キャッシュ破棄エラー {0}: {1}", + "DialogShaderDeletionMessage": "シェーダキャッシュを破棄しようとしています:\n\n{0}\n\n実行してよろしいですか?", + "DialogShaderDeletionErrorMessage": "シェーダキャッシュ破棄エラー {0}: {1}", + "DialogRyujinxErrorMessage": "エラーが発生しました", + "DialogInvalidTitleIdErrorMessage": "UI エラー: 選択されたゲームは有効なタイトル ID を保持していません", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "{0} には有効なシステムファームウェアがありません.", + "DialogFirmwareInstallerFirmwareInstallTitle": "ファームウェア {0} をインストール", + "DialogFirmwareInstallerFirmwareInstallMessage": "システムバージョン {0} がインストールされます.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n現在のシステムバージョン {0} を置き換えます.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n続けてよろしいですか?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "ファームウェアをインストール中...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "システムバージョン {0} が正常にインストールされました.", + "DialogUserProfileDeletionWarningMessage": "選択されたプロファイルを削除すると,プロファイルがひとつも存在しなくなります", + "DialogUserProfileDeletionConfirmMessage": "選択されたプロファイルを削除しますか", + "DialogControllerSettingsModifiedConfirmMessage": "現在のコントローラ設定が更新されました.", + "DialogControllerSettingsModifiedConfirmSubMessage": "セーブしますか?", + "DialogLoadNcaErrorMessage": "{0}. エラー発生ファイル: {1}", + "DialogDlcNoDlcErrorMessage": "選択されたファイルはこのタイトル用の DLC ではありません!", + "DialogPerformanceCheckLoggingEnabledMessage": "トレースロギングを有効にします. これは開発者のみに有用な機能です.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "パフォーマンス最適化のためには,トレースロギングを無効にすることを推奨します. トレースロギングを無効にしてよろしいですか?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "シェーダダンプを有効にします. これは開発者のみに有用な機能です.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "パフォーマンス最適化のためには, シェーダダンプを無効にすることを推奨します. シェーダダンプを無効にしてよろしいですか?", + "DialogLoadAppGameAlreadyLoadedMessage": "ゲームはすでにロード済みです", + "DialogLoadAppGameAlreadyLoadedSubMessage": "別のゲームを起動する前に, エミュレーションを停止またはエミュレータを閉じてください.", + "DialogUpdateAddUpdateErrorMessage": "選択されたファイルはこのタイトル用のアップデートではありません!", + "DialogSettingsBackendThreadingWarningTitle": "警告 - バックエンドスレッディング", + "DialogSettingsBackendThreadingWarningMessage": "このオプションの変更を完全に適用するには Ryujinx の再起動が必要です. プラットフォームによっては, Ryujinx のものを使用する前に手動でドライバ自身のマルチスレッディングを無効にする必要があるかもしれません.", + "SettingsTabGraphicsFeaturesOptions": "機能", + "SettingsTabGraphicsBackendMultithreading": "グラフィックスバックエンドのマルチスレッド実行:", + "CommonAuto": "自動", + "CommonOff": "オフ", + "CommonOn": "オン", + "InputDialogYes": "はい", + "InputDialogNo": "いいえ", + "DialogProfileInvalidProfileNameErrorMessage": "プロファイル名に無効な文字が含まれています. 再度試してみてください.", + "MenuBarOptionsPauseEmulation": "中断", + "MenuBarOptionsResumeEmulation": "再開", + "AboutUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx のウェブサイトを開きます.", + "AboutDisclaimerMessage": "Ryujinx は Nintendo™ および\nそのパートナー企業とは一切関係ありません.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) は\nAmiibo エミュレーションに使用されています.", + "AboutPatreonUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Patreon ページを開きます.", + "AboutGithubUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Github ページを開きます.", + "AboutDiscordUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Discord サーバを開きます.", + "AboutTwitterUrlTooltipMessage": "クリックするとデフォルトのブラウザで Ryujinx の Twitter ページを開きます.", + "AboutRyujinxAboutTitle": "Ryujinx について:", + "AboutRyujinxAboutContent": "Ryujinx は Nintendo Switch™ のエミュレータです.\nPatreon で私達の活動を支援してください.\n最新の情報は Twitter または Discord から取得できます.\n貢献したい開発者の方は GitHub または Discord で詳細をご確認ください.", + "AboutRyujinxMaintainersTitle": "開発者:", + "AboutRyujinxMaintainersContentTooltipMessage": "クリックするとデフォルトのブラウザで 貢献者のページを開きます.", + "AboutRyujinxSupprtersTitle": "Patreon での支援者:", + "AmiiboSeriesLabel": "Amiibo シリーズ", + "AmiiboCharacterLabel": "キャラクタ", + "AmiiboScanButtonLabel": "スキャン", + "AmiiboOptionsShowAllLabel": "すべての Amiibo を表示", + "AmiiboOptionsUsRandomTagLabel": "ハック: ランダムな Uuid を使用", + "DlcManagerTableHeadingEnabledLabel": "有効", + "DlcManagerTableHeadingTitleIdLabel": "タイトルID", + "DlcManagerTableHeadingContainerPathLabel": "コンテナパス", + "DlcManagerTableHeadingFullPathLabel": "フルパス", + "DlcManagerRemoveAllButton": "すべて削除", + "DlcManagerEnableAllButton": "すべて有効", + "DlcManagerDisableAllButton": "すべて無効", + "MenuBarOptionsChangeLanguage": "言語を変更", + "CommonSort": "並べ替え", + "CommonShowNames": "名称を表示", + "CommonFavorite": "お気に入り", + "OrderAscending": "昇順", + "OrderDescending": "降順", + "SettingsTabGraphicsFeatures": "機能", + "ErrorWindowTitle": "エラーウインドウ", + "ToggleDiscordTooltip": "Discord の \"現在プレイ中\" アクティビティに Ryujinx を表示するかどうかを選択します", + "AddGameDirBoxTooltip": "リストに追加するゲームディレクトリを入力します", + "AddGameDirTooltip": "リストにゲームディレクトリを追加します", + "RemoveGameDirTooltip": "選択したゲームディレクトリを削除します", + "CustomThemeCheckTooltip": "エミュレータのメニュー外観を変更するためカスタム Avalonia テーマを使用します", + "CustomThemePathTooltip": "カスタム GUI テーマのパスです", + "CustomThemeBrowseTooltip": "カスタム GUI テーマを参照します", + "DockModeToggleTooltip": "有効にすると,ドッキングされた Nintendo Switch をエミュレートします.多くのゲームではグラフィックス品質が向上します.\n無効にすると,携帯モードの Nintendo Switch をエミュレートします.グラフィックスの品質は低下します.\n\nドッキングモード有効ならプレイヤー1の,無効なら携帯の入力を設定してください.\n\nよくわからない場合はオンのままにしてください.", + "DirectKeyboardTooltip": "キーボード直接アクセス (HID) に対応します. キーボードをテキスト入力デバイスとして使用できます.", + "DirectMouseTooltip": "マウス直接アクセス (HID) に対応します. マウスをポインティングデバイスとして使用できます.", + "RegionTooltip": "システムの地域を変更します", + "LanguageTooltip": "システムの言語を変更します", + "TimezoneTooltip": "システムのタイムゾーンを変更します", + "TimeTooltip": "システムの時刻を変更します", + "VSyncToggleTooltip": "エミュレートされたゲーム機の垂直同期です. 多くのゲームにおいて, フレームリミッタとして機能します. 無効にすると, ゲームが高速で実行されたり, ロード中に時間がかかったり, 止まったりすることがあります.\n\n設定したホットキーで, ゲーム内で切り替え可能です. 無効にする場合は, この操作を行うことをおすすめします.\n\nよくわからない場合はオンのままにしてください.", + "PptcToggleTooltip": "翻訳されたJIT関数をセーブすることで, ゲームをロードするたびに毎回翻訳する処理を不要とします.\n\n一度ゲームを起動すれば,二度目以降の起動時遅延を大きく軽減できます.\n\nよくわからない場合はオンのままにしてください.", + "FsIntegrityToggleTooltip": "ゲーム起動時にファイル破損をチェックし,破損が検出されたらログにハッシュエラーを表示します..\n\nパフォーマンスには影響なく, トラブルシューティングに役立ちます.\n\nよくわからない場合はオンのままにしてください.", + "AudioBackendTooltip": "音声レンダリングに使用するバックエンドを変更します.\n\nSDL2 が優先され, OpenAL と SoundIO はフォールバックとして使用されます. ダミーは音声出力しません.\n\nよくわからない場合は SDL2 を設定してください.", + "MemoryManagerTooltip": "ゲストメモリのマップ/アクセス方式を変更します. エミュレートされるCPUのパフォーマンスに大きな影響を与えます.\n\nよくわからない場合は「ホスト,チェックなし」を設定してください.", + "MemoryManagerSoftwareTooltip": "アドレス変換にソフトウェアページテーブルを使用します. 非常に正確ですがパフォーマンスが大きく低下します.", + "MemoryManagerHostTooltip": "ホストのアドレス空間にメモリを直接マップします.JITのコンパイルと実行速度が大きく向上します.", + "MemoryManagerUnsafeTooltip": "メモリを直接マップしますが, アクセス前にゲストのアドレス空間内のアドレスをマスクしません. より高速になりますが, 安全性が犠牲になります. ゲストアプリケーションは Ryujinx のどこからでもメモリにアクセスできるので,このモードでは信頼できるプログラムだけを実行するようにしてください.", + "DRamTooltip": "エミュレートされたシステムのメモリ容量を 4GiB から 6GiB に増加します.\n\n高解像度のテクスチャパックや 4K解像度の mod を使用する場合に有用です. パフォーマンスを改善するものではありません.\n\nよくわからない場合はオフのままにしてください.", + "IgnoreMissingServicesTooltip": "未実装の Horizon OS サービスを無視します. 特定のゲームにおいて起動時のクラッシュを回避できる場合があります.\n\nよくわからない場合はオフのままにしてください.", + "GraphicsBackendThreadingTooltip": "グラフィックスバックエンドのコマンドを別スレッドで実行します.\n\nシェーダのコンパイルを高速化し, 遅延を軽減し, マルチスレッド非対応の GPU ドライバにおいてパフォーマンスを改善します. マルチスレッド対応のドライバでも若干パフォーマンス改善が見られます.\n\nよくわからない場合は自動に設定してください.", + "GalThreadingTooltip": "グラフィックスバックエンドのコマンドを別スレッドで実行します.\n\nシェーダのコンパイルを高速化し, 遅延を軽減し, マルチスレッド非対応の GPU ドライバにおいてパフォーマンスを改善します. マルチスレッド対応のドライバでも若干パフォーマンス改善が見られます.\n\nよくわからない場合は自動に設定してください.", + "ShaderCacheToggleTooltip": "ディスクシェーダキャッシュをセーブし,次回以降の実行時遅延を軽減します.\n\nよくわからない場合はオンのままにしてください.", + "ResolutionScaleTooltip": "レンダリングに適用される解像度の倍率です", + "ResolutionScaleEntryTooltip": "1.5 のような整数でない倍率を指定すると,問題が発生したりクラッシュしたりする場合があります.", + "AnisotropyTooltip": "異方性フィルタリングのレベルです (ゲームが要求する値を使用する場合は「自動」を設定してください)", + "AspectRatioTooltip": "レンダリングに適用されるアスペクト比です.", + "ShaderDumpPathTooltip": "グラフィックス シェーダダンプのパスです", + "FileLogTooltip": "コンソール出力されるログをディスク上のログファイルにセーブします. パフォーマンスには影響を与えません.", + "StubLogTooltip": "stub ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "InfoLogTooltip": "info ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "WarnLogTooltip": "warning ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "ErrorLogTooltip": "error ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "TraceLogTooltip": "trace ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "GuestLogTooltip": "guest ログメッセージをコンソールに出力します. パフォーマンスには影響を与えません.", + "FileAccessLogTooltip": "ファイルアクセスログメッセージをコンソールに出力します.", + "FSAccessLogModeTooltip": "コンソールへのファイルシステムアクセスログ出力を有効にします.0-3 のモードが有効です", + "DeveloperOptionTooltip": "使用上の注意", + "OpenGlLogLevel": "適切なログレベルを有効にする必要があります", + "DebugLogTooltip": "デバッグログメッセージをコンソールに出力します.\n\nログが読みづらくなり,エミュレータのパフォーマンスが低下するため,開発者から特別な指示がある場合のみ使用してください.", + "LoadApplicationFileTooltip": "ロードする Switch 互換のファイルを選択するためファイルエクスプローラを開きます", + "LoadApplicationFolderTooltip": "ロードする Switch 互換の展開済みアプリケーションを選択するためファイルエクスプローラを開きます", + "OpenRyujinxFolderTooltip": "Ryujinx ファイルシステムフォルダを開きます", + "OpenRyujinxLogsTooltip": "ログが格納されるフォルダを開きます", + "ExitTooltip": "Ryujinx を終了します", + "OpenSettingsTooltip": "設定ウインドウを開きます", + "OpenProfileManagerTooltip": "ユーザプロファイル管理ウインドウを開きます", + "StopEmulationTooltip": "ゲームのエミュレーションを停止してゲーム選択画面に戻ります", + "CheckUpdatesTooltip": "Ryujinx のアップデートを確認します", + "OpenAboutTooltip": "Ryujinx についてのウインドウを開きます", + "GridSize": "グリッドサイズ", + "GridSizeTooltip": "グリッドサイズを変更します", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "ポルトガル語(ブラジル)", + "AboutRyujinxContributorsButtonHeader": "すべての貢献者を確認", + "SettingsTabSystemAudioVolume": "音量: ", + "AudioVolumeTooltip": "音量を変更します", + "SettingsTabSystemEnableInternetAccess": "ゲストインターネットアクセス / LAN モード", + "EnableInternetAccessTooltip": "エミュレートしたアプリケーションをインターネットに接続できるようにします.\n\nLAN モードを持つゲーム同士は,この機能を有効にして同じアクセスポイントに接続すると接続できます. 実機も含まれます.\n\n任天堂のサーバーには接続できません. インターネットに接続しようとすると,特定のゲームでクラッシュすることがあります.\n\nよくわからない場合はオフのままにしてください.", + "GameListContextMenuManageCheatToolTip": "チートを管理します", + "GameListContextMenuManageCheat": "チートを管理", + "ControllerSettingsStickRange": "範囲:", + "DialogStopEmulationTitle": "Ryujinx - エミュレーションを停止", + "DialogStopEmulationMessage": "エミュレーションを停止してよろしいですか?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "音声", + "SettingsTabNetwork": "ネットワーク", + "SettingsTabNetworkConnection": "ネットワーク接続", + "SettingsTabCpuCache": "CPU キャッシュ", + "SettingsTabCpuMemory": "CPU メモリ", + "DialogUpdaterFlatpakNotSupportedMessage": "FlatHub を使用して Ryujinx をアップデートしてください.", + "UpdaterDisabledWarningTitle": "アップデータは無効です!", + "GameListContextMenuOpenSdModsDirectory": "Atmosphere Mods ディレクトリを開く", + "GameListContextMenuOpenSdModsDirectoryToolTip": "アプリケーションの Mod データを格納する SD カードの Atmosphere ディレクトリを開きます. 実際のハードウェア用にパッケージされた Mod データに有用です.", + "ControllerSettingsRotate90": "時計回りに 90° 回転", + "IconSize": "アイコンサイズ", + "IconSizeTooltip": "ゲームアイコンのサイズを変更します", + "MenuBarOptionsShowConsole": "コンソールを表示", + "ShaderCachePurgeError": "シェーダキャッシュの破棄エラー {0}: {1}", + "UserErrorNoKeys": "Keys がありません", + "UserErrorNoFirmware": "ファームウェアがありません", + "UserErrorFirmwareParsingFailed": "ファームウェアのパーズエラー", + "UserErrorApplicationNotFound": "アプリケーションがありません", + "UserErrorUnknown": "不明なエラー", + "UserErrorUndefined": "未定義エラー", + "UserErrorNoKeysDescription": "'prod.keys' が見つかりませんでした", + "UserErrorNoFirmwareDescription": "インストールされたファームウェアが見つかりませんでした", + "UserErrorFirmwareParsingFailedDescription": "ファームウェアをパーズできませんでした.通常,古いキーが原因です.", + "UserErrorApplicationNotFoundDescription": "指定されたパスに有効なアプリケーションがありませんでした.", + "UserErrorUnknownDescription": "不明なエラーが発生しました!", + "UserErrorUndefinedDescription": "未定義のエラーが発生しました! 発生すべきものではないので,開発者にご連絡ください!", + "OpenSetupGuideMessage": "セットアップガイドを開く", + "NoUpdate": "アップデートなし", + "TitleUpdateVersionLabel": "バージョン {0} - {1}", + "RyujinxInfo": "Ryujinx - 情報", + "RyujinxConfirm": "Ryujinx - 確認", + "FileDialogAllTypes": "すべての種別", + "Never": "Never", + "SwkbdMinCharacters": "最低 {0} 文字必要です", + "SwkbdMinRangeCharacters": "{0}-{1} 文字にしてください", + "SoftwareKeyboard": "ソフトウェアキーボード", + "DialogControllerAppletMessagePlayerRange": "アプリケーションは {0} 名のプレイヤーを要求しています:\n\n種別: {1}\n\nプレイヤー: {2}\n\n{3}設定を開き各プレイヤーの入力設定を行ってから閉じるを押してください.", + "DialogControllerAppletMessage": "アプリケーションは {0} 名のプレイヤーを要求しています:\n\n種別: {1}\n\nプレイヤー: {2}\n\n{3}設定を開き各プレイヤーの入力設定を行ってから閉じるを押してください.", + "DialogControllerAppletDockModeSet": "ドッキングモードに設定されました. 携帯モードは無効になります.\n\n", + "UpdaterRenaming": "古いファイルをリネーム中...", + "UpdaterRenameFailed": "ファイルをリネームできませんでした: {0}", + "UpdaterAddingFiles": "新規ファイルを追加中...", + "UpdaterExtracting": "アップデートを展開中...", + "UpdaterDownloading": "アップデートをダウンロード中...", + "Game": "ゲーム", + "Docked": "ドッキング", + "Handheld": "携帯", + "ConnectionError": "接続エラー.", + "AboutPageDeveloperListMore": "{0}, その他大勢...", + "ApiError": "API エラー.", + "LoadingHeading": "ロード中: {0}", + "CompilingPPTC": "PTC をコンパイル中", + "CompilingShaders": "シェーダをコンパイル中", + "AllKeyboards": "すべてのキーボード", + "OpenFileDialogTitle": "開くファイルを選択", + "OpenFolderDialogTitle": "展開されたゲームフォルダを選択", + "AllSupportedFormats": "すべての対応フォーマット", + "RyujinxUpdater": "Ryujinx アップデータ", + "SettingsTabHotkeys": "キーボード ホットキー", + "SettingsTabHotkeysHotkeys": "キーボード ホットキー", + "SettingsTabHotkeysToggleVsyncHotkey": "VSync 切り替え:", + "SettingsTabHotkeysScreenshotHotkey": "スクリーンショット:", + "SettingsTabHotkeysShowUiHotkey": "UI表示:", + "SettingsTabHotkeysPauseHotkey": "中断:", + "SettingsTabHotkeysToggleMuteHotkey": "ミュート:", + "ControllerMotionTitle": "モーションコントロール設定", + "ControllerRumbleTitle": "振動設定", + "SettingsSelectThemeFileDialogTitle": "テーマファイルを選択", + "SettingsXamlThemeFile": "Xaml テーマファイル", + "AvatarWindowTitle": "アカウント - アバター管理", + "Amiibo": "Amiibo", + "Unknown": "不明", + "Usage": "使用法", + "Writable": "書き込み可能", + "SelectDlcDialogTitle": "DLC ファイルを選択", + "SelectUpdateDialogTitle": "アップデートファイルを選択", + "UserProfileWindowTitle": "ユーザプロファイルを管理", + "CheatWindowTitle": "チート管理", + "DlcWindowTitle": "DLC 管理", + "UpdateWindowTitle": "アップデート管理", + "CheatWindowHeading": "利用可能なチート {0} [{1}]", + "DlcWindowHeading": "利用可能な DLC {0} [{1}]", + "UserProfilesEditProfile": "編集", + "Cancel": "キャンセル", + "Save": "セーブ", + "Discard": "破棄", + "UserProfilesSetProfileImage": "プロファイル画像を設定", + "UserProfileEmptyNameError": "名称が必要です", + "UserProfileNoImageError": "プロファイル画像が必要です", + "GameUpdateWindowHeading": "利用可能なアップデート {0} [{1}]", + "SettingsTabHotkeysResScaleUpHotkey": "解像度を上げる:", + "SettingsTabHotkeysResScaleDownHotkey": "解像度を下げる:", + "UserProfilesName": "名称:", + "UserProfilesUserId": "ユーザID:", + "SettingsTabGraphicsBackend": "グラフィックスバックエンド", + "SettingsTabGraphicsBackendTooltip": "使用するグラフィックスバックエンドです", + "SettingsEnableTextureRecompression": "テクスチャの再圧縮を有効", + "SettingsEnableTextureRecompressionTooltip": "VRAMの使用量を削減するためテクスチャを圧縮します.\n\nGPUのVRAMが4GiB未満の場合は使用を推奨します.\n\nよくわからない場合はオフのままにしてください.", + "SettingsTabGraphicsPreferredGpu": "優先使用するGPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Vulkanグラフィックスバックエンドで使用されるグラフィックスカードを選択します.\n\nOpenGLが使用するGPUには影響しません.\n\n不明な場合は, \"dGPU\" としてフラグが立っているGPUに設定します. ない場合はそのままにします.", + "SettingsAppRequiredRestartMessage": "Ryujinx の再起動が必要です", + "SettingsGpuBackendRestartMessage": "グラフィックスバックエンドまたはGPUの設定が変更されました. 変更を適用するには再起動する必要があります", + "SettingsGpuBackendRestartSubMessage": "今すぐ再起動しますか?", + "RyujinxUpdaterMessage": "Ryujinx を最新版にアップデートしますか?", + "SettingsTabHotkeysVolumeUpHotkey": "音量を上げる:", + "SettingsTabHotkeysVolumeDownHotkey": "音量を下げる:", + "SettingsEnableMacroHLE": "マクロの高レベルエミュレーション (HLE) を有効", + "SettingsEnableMacroHLETooltip": "GPU マクロコードの高レベルエミュレーションです.\n\nパフォーマンスを向上させますが, 一部のゲームでグラフィックに不具合が発生する可能性があります.\n\nよくわからない場合はオンのままにしてください.", + "VolumeShort": "音量", + "UserProfilesManageSaves": "セーブデータの管理", + "DeleteUserSave": "このゲームのユーザセーブデータを削除しますか?", + "IrreversibleActionNote": "この操作は元に戻せません.", + "SaveManagerHeading": "{0} のセーブデータを管理", + "SaveManagerTitle": "セーブデータマネージャ", + "Name": "名称", + "Size": "サイズ", + "Search": "検索", + "UserProfilesRecoverLostAccounts": "アカウントの復旧", + "Recover": "復旧", + "UserProfilesRecoverHeading": "以下のアカウントのセーブデータが見つかりました" +} diff --git a/src/Ryujinx.Ava/Assets/Locales/ko_KR.json b/src/Ryujinx.Ava/Assets/Locales/ko_KR.json new file mode 100644 index 00000000..adf7f61e --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/ko_KR.json @@ -0,0 +1,614 @@ +{ + "Language": "한국어", + "MenuBarFileOpenApplet": "애플릿 열기", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "독립 실행형 모드에서 Mii 편집기 애플릿 열기", + "SettingsTabInputDirectMouseAccess": "직접 마우스 접속", + "SettingsTabSystemMemoryManagerMode": "메모리 관리자 모드 :", + "SettingsTabSystemMemoryManagerModeSoftware": "소프트웨어", + "SettingsTabSystemMemoryManagerModeHost": "호스트 (빠름)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "호스트가 확인되지 않음 (가장 빠르나 안전하지 않음)", + "MenuBarFile": "_파일", + "MenuBarFileOpenFromFile": "_파일에서 응용 프로그램 불러오기", + "MenuBarFileOpenUnpacked": "_압축을 푼 게임 불러오기", + "MenuBarFileOpenEmuFolder": "Ryujinx 폴더 열기", + "MenuBarFileOpenLogsFolder": "로그 폴더 열기", + "MenuBarFileExit": "_종료", + "MenuBarOptions": "옵션", + "MenuBarOptionsToggleFullscreen": "전체화면 전환", + "MenuBarOptionsStartGamesInFullscreen": "전체 화면 모드에서 게임 시작", + "MenuBarOptionsStopEmulation": "에뮬레이션 중지", + "MenuBarOptionsSettings": "_설정", + "MenuBarOptionsManageUserProfiles": "_사용자 프로파일 관리\n", + "MenuBarActions": "_동작", + "MenuBarOptionsSimulateWakeUpMessage": "모닝콜 메세지 시뮬레이션\n", + "MenuBarActionsScanAmiibo": "Amiibo 스캔", + "MenuBarTools": "_도구", + "MenuBarToolsInstallFirmware": "펌웨어 설치", + "MenuBarFileToolsInstallFirmwareFromFile": "XCI 또는 ZIP에서 펌웨어 설치", + "MenuBarFileToolsInstallFirmwareFromDirectory": "디렉토리에서 펌웨어 설치", + "MenuBarHelp": "도움", + "MenuBarHelpCheckForUpdates": "업데이트 확인", + "MenuBarHelpAbout": "정보", + "MenuSearch": "검색...", + "GameListHeaderFavorite": "즐겨찾기", + "GameListHeaderIcon": "아이콘", + "GameListHeaderApplication": "이름", + "GameListHeaderDeveloper": "개발자", + "GameListHeaderVersion": "버전", + "GameListHeaderTimePlayed": "플레이 시간", + "GameListHeaderLastPlayed": "마지막 플레이", + "GameListHeaderFileExtension": "파일 확장자", + "GameListHeaderFileSize": "파일 크기", + "GameListHeaderPath": "경로", + "GameListContextMenuOpenUserSaveDirectory": "사용자 저장 디렉토리 열기\n", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "응용 프로그램의 사용자 저장이 포함된 디렉토리 열기\n", + "GameListContextMenuOpenDeviceSaveDirectory": "사용자 장치 디렉토리 열기", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "응용 프로그램의 장치 저장이 포함된 디렉토리 열기\n", + "GameListContextMenuOpenBcatSaveDirectory": "사용자의 BCAT 디렉토리 열기\n", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "응용 프로그램의 BCAT 저장이 포함된 디렉토리 열기\n", + "GameListContextMenuManageTitleUpdates": "타이틀 업데이트 관리\n", + "GameListContextMenuManageTitleUpdatesToolTip": "타이틀 업데이트 관리 창 열기", + "GameListContextMenuManageDlc": "DLC 관리", + "GameListContextMenuManageDlcToolTip": "DLC 관리 창 열기", + "GameListContextMenuOpenModsDirectory": "Mod 디렉토리 열기", + "GameListContextMenuOpenModsDirectoryToolTip": "응용 프로그램의 Mod들이 포함된 디렉터리 열기", + "GameListContextMenuCacheManagement": "캐시 관리", + "GameListContextMenuCacheManagementPurgePptc": "대기열 PPTC 재구성", + "GameListContextMenuCacheManagementPurgePptcToolTip": "다음 게임 시작 시 부팅 시 PPTC가 다시 빌드되도록 트리거\n", + "GameListContextMenuCacheManagementPurgeShaderCache": "셰이더 캐시 제거", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "응용 프로그램 셰이더 캐시 삭제\n", + "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC 디렉토리 열기", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "응용 프로그램 PPTC 캐시가 포함된 디렉터리 열기\n", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "셰이더 캐시 디렉토리 열기", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "응용 프로그램 셰이더 캐시가 포함된 디렉터리 열기\n", + "GameListContextMenuExtractData": "데이터 추출", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "애플리케이션의 현재 구성에서 ExeFS 추출 (업데이트 포함)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "응용 프로그램의 현재 구성에서 RomFS 추출 (업데이트 포함)\n", + "GameListContextMenuExtractDataLogo": "로고", + "GameListContextMenuExtractDataLogoToolTip": "응용 프로그램의 현재 구성에서 로고 섹션 추출 (업데이트 포함)\n", + "StatusBarGamesLoaded": "불러온 {0}/{1} 개의 게임", + "StatusBarSystemVersion": "시스템 버전 : {0}", + "Settings": "설정", + "SettingsTabGeneral": "사용자 인터페이스", + "SettingsTabGeneralGeneral": "일반", + "SettingsTabGeneralEnableDiscordRichPresence": "디스코드 활동 상태 활성화\n", + "SettingsTabGeneralCheckUpdatesOnLaunch": "실행 시 업데이트 확인\n", + "SettingsTabGeneralShowConfirmExitDialog": "\"종료 확인\" 대화 상자 표시", + "SettingsTabGeneralHideCursorOnIdle": "유휴 상태에서 커서 숨기기", + "SettingsTabGeneralGameDirectories": "게임 디렉토리", + "SettingsTabGeneralAdd": "추가", + "SettingsTabGeneralRemove": "제거", + "SettingsTabSystem": "시스템", + "SettingsTabSystemCore": "코어", + "SettingsTabSystemSystemRegion": "시스템 지역 :", + "SettingsTabSystemSystemRegionJapan": "일본", + "SettingsTabSystemSystemRegionUSA": "미국", + "SettingsTabSystemSystemRegionEurope": "유럽", + "SettingsTabSystemSystemRegionAustralia": "호주", + "SettingsTabSystemSystemRegionChina": "중국", + "SettingsTabSystemSystemRegionKorea": "한국", + "SettingsTabSystemSystemRegionTaiwan": "대만", + "SettingsTabSystemSystemLanguage": "시스템 언어 :", + "SettingsTabSystemSystemLanguageJapanese": "일본어", + "SettingsTabSystemSystemLanguageAmericanEnglish": "영어(미국)", + "SettingsTabSystemSystemLanguageFrench": "프랑스어", + "SettingsTabSystemSystemLanguageGerman": "독일어", + "SettingsTabSystemSystemLanguageItalian": "이탈리아어", + "SettingsTabSystemSystemLanguageSpanish": "스페인어", + "SettingsTabSystemSystemLanguageChinese": "중국어", + "SettingsTabSystemSystemLanguageKorean": "한국어", + "SettingsTabSystemSystemLanguageDutch": "네덜란드어", + "SettingsTabSystemSystemLanguagePortuguese": "포르투갈어", + "SettingsTabSystemSystemLanguageRussian": "러시아어", + "SettingsTabSystemSystemLanguageTaiwanese": "대만어", + "SettingsTabSystemSystemLanguageBritishEnglish": "영어 (영국)", + "SettingsTabSystemSystemLanguageCanadianFrench": "프랑스어 (캐나다)", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "스페인어 (라틴 아메리카)", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "중국어 간체", + "SettingsTabSystemSystemLanguageTraditionalChinese": "중국어 번체", + "SettingsTabSystemSystemTimeZone": "시스템 시간대 :", + "SettingsTabSystemSystemTime": "시스템 시간 :", + "SettingsTabSystemEnableVsync": "수직 동기화", + "SettingsTabSystemEnablePptc": "PPTC (프로파일된 영구 번역 캐시) 활성화", + "SettingsTabSystemEnableFsIntegrityChecks": "FS 무결성 검사", + "SettingsTabSystemAudioBackend": "오디오 백엔드:", + "SettingsTabSystemAudioBackendDummy": "더미", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "사운드IO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "해킹", + "SettingsTabSystemHacksNote": " (불안정을 일으킬 수 있음)", + "SettingsTabSystemExpandDramSize": "대체 메모리 레이아웃 사용 (개발자)\n", + "SettingsTabSystemIgnoreMissingServices": "누락된 서비스 무시", + "SettingsTabGraphics": "그래픽", + "SettingsTabGraphicsAPI": "그래픽 API", + "SettingsTabGraphicsEnableShaderCache": "셰이더 캐시 활성화", + "SettingsTabGraphicsAnisotropicFiltering": "이방성 필터링:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "자동", + "SettingsTabGraphicsAnisotropicFiltering2x": "2 배", + "SettingsTabGraphicsAnisotropicFiltering4x": "4 배", + "SettingsTabGraphicsAnisotropicFiltering8x": "8 배", + "SettingsTabGraphicsAnisotropicFiltering16x": "16 배", + "SettingsTabGraphicsResolutionScale": "해상도 스케일:", + "SettingsTabGraphicsResolutionScaleCustom": "사용자 지정 (권장하지 않음)", + "SettingsTabGraphicsResolutionScaleNative": "기본 (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2 배 (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3 배 (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4 배 (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "화면비:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "창에 맞게 늘리기", + "SettingsTabGraphicsDeveloperOptions": "개발자 옵션", + "SettingsTabGraphicsShaderDumpPath": "그래픽 쉐이더 덤프 경로:", + "SettingsTabLogging": "로깅", + "SettingsTabLoggingLogging": "로깅", + "SettingsTabLoggingEnableLoggingToFile": "파일에 로깅 활성화", + "SettingsTabLoggingEnableStubLogs": "스텁 로그 활성화", + "SettingsTabLoggingEnableInfoLogs": "정보 로그 활성화", + "SettingsTabLoggingEnableWarningLogs": "경고 로그 활성화", + "SettingsTabLoggingEnableErrorLogs": "오류 로그 활성화", + "SettingsTabLoggingEnableTraceLogs": "추적 로그 활성화", + "SettingsTabLoggingEnableGuestLogs": "게스트 로그 활성화", + "SettingsTabLoggingEnableFsAccessLogs": "Fs 접속 로그 활성화", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs 글로벌 접속 로그 모드:", + "SettingsTabLoggingDeveloperOptions": "개발자 옵션 (경고 : 성능이 저하됨)", + "SettingsTabLoggingGraphicsBackendLogLevel": "그래픽 백엔드 로그 수준:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "없음", + "SettingsTabLoggingGraphicsBackendLogLevelError": "오류", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "감속", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "모두", + "SettingsTabLoggingEnableDebugLogs": "디버그 로그 활성화", + "SettingsTabInput": "입력", + "SettingsTabInputEnableDockedMode": "도킹 모드", + "SettingsTabInputDirectKeyboardAccess": "직접 키보드 접속", + "SettingsButtonSave": "저장", + "SettingsButtonClose": "닫기", + "SettingsButtonOk": "확인", + "SettingsButtonCancel": "취소", + "SettingsButtonApply": "적용", + "ControllerSettingsPlayer": "플레이어", + "ControllerSettingsPlayer1": "플레이어 1", + "ControllerSettingsPlayer2": "플레이어 2", + "ControllerSettingsPlayer3": "플레이어 3", + "ControllerSettingsPlayer4": "플레이어 4", + "ControllerSettingsPlayer5": "플레이어 5", + "ControllerSettingsPlayer6": "플레이어 6", + "ControllerSettingsPlayer7": "플레이어 7", + "ControllerSettingsPlayer8": "플레이어 8", + "ControllerSettingsHandheld": "휴대용", + "ControllerSettingsInputDevice": "입력 장치", + "ControllerSettingsRefresh": "새로 고침", + "ControllerSettingsDeviceDisabled": "비활성화됨", + "ControllerSettingsControllerType": "컨트롤러 유형", + "ControllerSettingsControllerTypeHandheld": "휴대용", + "ControllerSettingsControllerTypeProController": "프로 컨트롤러", + "ControllerSettingsControllerTypeJoyConPair": "조이콘 페어링", + "ControllerSettingsControllerTypeJoyConLeft": "왼쪽 조이콘", + "ControllerSettingsControllerTypeJoyConRight": "오른쪽 조이콘", + "ControllerSettingsProfile": "프로파일", + "ControllerSettingsProfileDefault": "기본", + "ControllerSettingsLoad": "불러오기", + "ControllerSettingsAdd": "추가", + "ControllerSettingsRemove": "제거", + "ControllerSettingsButtons": "버튼", + "ControllerSettingsButtonA": "A 버튼", + "ControllerSettingsButtonB": "B 버튼", + "ControllerSettingsButtonX": "X 버튼", + "ControllerSettingsButtonY": "Y 버튼", + "ControllerSettingsButtonPlus": "+ 버튼", + "ControllerSettingsButtonMinus": "- 버튼", + "ControllerSettingsDPad": "방향 패드", + "ControllerSettingsDPadUp": "위쪽", + "ControllerSettingsDPadDown": "아래쪽", + "ControllerSettingsDPadLeft": "왼쪽", + "ControllerSettingsDPadRight": "오른쪽", + "ControllerSettingsLStick": "왼쪽 스틱", + "ControllerSettingsLStickButton": "버튼", + "ControllerSettingsLStickUp": "위쪽", + "ControllerSettingsLStickDown": "아래쪽", + "ControllerSettingsLStickLeft": "왼쪽", + "ControllerSettingsLStickRight": "오른쪽", + "ControllerSettingsLStickStick": "스틱", + "ControllerSettingsLStickInvertXAxis": "스틱 X 축 반전", + "ControllerSettingsLStickInvertYAxis": "스틱 Y 축 반전", + "ControllerSettingsLStickDeadzone": "데드존:", + "ControllerSettingsRStick": "오른쪽 스틱", + "ControllerSettingsRStickButton": "버튼", + "ControllerSettingsRStickUp": "위쪽", + "ControllerSettingsRStickDown": "아래쪽", + "ControllerSettingsRStickLeft": "왼쪽", + "ControllerSettingsRStickRight": "오른쪽", + "ControllerSettingsRStickStick": "스틱", + "ControllerSettingsRStickInvertXAxis": "스틱 X 축 반전", + "ControllerSettingsRStickInvertYAxis": "스틱 Y 축 반전", + "ControllerSettingsRStickDeadzone": "데드존:", + "ControllerSettingsTriggersLeft": "왼쪽 트리거", + "ControllerSettingsTriggersRight": "오른쪽 트리거", + "ControllerSettingsTriggersButtonsLeft": "왼쪽 트리거 버튼", + "ControllerSettingsTriggersButtonsRight": "오른쪽 트리거 버튼", + "ControllerSettingsTriggers": "트리거 버튼", + "ControllerSettingsTriggerL": "L 버튼", + "ControllerSettingsTriggerR": "R 버튼", + "ControllerSettingsTriggerZL": "ZL 버튼", + "ControllerSettingsTriggerZR": "ZR 버튼", + "ControllerSettingsLeftSL": "SL 버튼", + "ControllerSettingsLeftSR": "SR 버튼", + "ControllerSettingsRightSL": "SL 버튼", + "ControllerSettingsRightSR": "SR 버튼", + "ControllerSettingsExtraButtonsLeft": "왼쪽 버튼", + "ControllerSettingsExtraButtonsRight": "오른쪽 버튼", + "ControllerSettingsMisc": "기타", + "ControllerSettingsTriggerThreshold": "트리거 임계값 :", + "ControllerSettingsMotion": "동작", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook 호환 모션 사용", + "ControllerSettingsMotionControllerSlot": "컨트롤러 슬롯:", + "ControllerSettingsMotionMirrorInput": "미러 입력", + "ControllerSettingsMotionRightJoyConSlot": "오른쪽 조이콘 슬롯 :", + "ControllerSettingsMotionServerHost": "서버 호스트:", + "ControllerSettingsMotionGyroSensitivity": "자이로 감도:", + "ControllerSettingsMotionGyroDeadzone": "자이로 데드존:", + "ControllerSettingsSave": "저장", + "ControllerSettingsClose": "닫기", + "UserProfilesSelectedUserProfile": "선택한 사용자 프로파일:", + "UserProfilesSaveProfileName": "프로파일 이름 저장", + "UserProfilesChangeProfileImage": "프로파일 이미지 변경", + "UserProfilesAvailableUserProfiles": "사용 가능한 사용자 프로파일:", + "UserProfilesAddNewProfile": "프로파일 생성", + "UserProfilesDeleteSelectedProfile": "선택한 프로파일 삭제", + "UserProfilesClose": "닫기", + "ProfileImageSelectionTitle": "프로파일 이미지 선택", + "ProfileImageSelectionHeader": "프로파일 이미지 선택", + "ProfileImageSelectionNote": "사용자 지정 프로파일 이미지를 가져오거나 시스템 펌웨어에서 아바타를 선택할 수 있음\n", + "ProfileImageSelectionImportImage": "이미지 파일 가져오기", + "ProfileImageSelectionSelectAvatar": "펌웨어 아바타 선택", + "InputDialogTitle": "입력 대화상자", + "InputDialogOk": "확인", + "InputDialogCancel": "취소", + "InputDialogAddNewProfileTitle": "프로파일 이름 선택", + "InputDialogAddNewProfileHeader": "프로파일 이름 입력", + "InputDialogAddNewProfileSubtext": "(최대 길이: {0})\n", + "AvatarChoose": "선택", + "AvatarSetBackgroundColor": "배경색 설정", + "AvatarClose": "닫기", + "ControllerSettingsLoadProfileToolTip": "프로파일 불러오기", + "ControllerSettingsAddProfileToolTip": "프로파일 추가", + "ControllerSettingsRemoveProfileToolTip": "프로파일 제거", + "ControllerSettingsSaveProfileToolTip": "프로파일 저장", + "MenuBarFileToolsTakeScreenshot": "스크린 샷 찍기", + "MenuBarFileToolsHideUi": "Hide UI", + "GameListContextMenuToggleFavorite": "즐겨찾기 전환", + "GameListContextMenuToggleFavoriteToolTip": "게임 즐겨찾기 상태 전환\n", + "SettingsTabGeneralTheme": "테마", + "SettingsTabGeneralThemeCustomTheme": "사용자 정의 테마 경로", + "SettingsTabGeneralThemeBaseStyle": "기본 스타일", + "SettingsTabGeneralThemeBaseStyleDark": "어두움", + "SettingsTabGeneralThemeBaseStyleLight": "밝음", + "SettingsTabGeneralThemeEnableCustomTheme": "사용자 정의 테마 활성화", + "ButtonBrowse": "찾아보기", + "ControllerSettingsConfigureGeneral": "구성", + "ControllerSettingsRumble": "진동", + "ControllerSettingsRumbleStrongMultiplier": "강력한 진동 증폭기", + "ControllerSettingsRumbleWeakMultiplier": "약한 진동 증폭기", + "DialogMessageSaveNotAvailableMessage": "{0} [{1:x16}]에 대한 저장 데이터가 없음\n", + "DialogMessageSaveNotAvailableCreateSaveMessage": "이 게임에 대한 저장 데이터를 생성하겠습니까?\n", + "DialogConfirmationTitle": "Ryujinx - 확인", + "DialogUpdaterTitle": "Ryujinx - 업데이터", + "DialogErrorTitle": "Ryujinx - 오류", + "DialogWarningTitle": "Ryujinx - 경고", + "DialogExitTitle": "Ryujinx - 종료", + "DialogErrorMessage": "Ryujinx 오류 발생", + "DialogExitMessage": "Ryujinx를 종료하겠습니까?", + "DialogExitSubMessage": "저장하지 않은 모든 데이터는 손실됩니다!", + "DialogMessageCreateSaveErrorMessage": "지정된 저장 데이터를 작성하는 중에 오류 발생: {0}\n", + "DialogMessageFindSaveErrorMessage": "지정된 저장 데이터를 찾는 중에 오류 발생: {0}\n", + "FolderDialogExtractTitle": "추출할 폴더 선택", + "DialogNcaExtractionMessage": "{1}에서 {0} 섹션을 추출하는 중...\n", + "DialogNcaExtractionTitle": "Ryujinx - NCA 섹션 추출기", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "추출 실패하였습니다. 선택한 파일에 기본 NCA가 없습니다.\n", + "DialogNcaExtractionCheckLogErrorMessage": "추출 실패하였습니다. 자세한 내용은 로그 파일을 읽으세요.\n", + "DialogNcaExtractionSuccessMessage": "추출이 성공적으로 완료되었습니다.", + "DialogUpdaterConvertFailedMessage": "현재 Ryujinx 버전을 변환하지 못했습니다.", + "DialogUpdaterCancelUpdateMessage": "업데이트 취소 중 입니다!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "이미 최신 버전의 Ryujinx를 사용하고 있습니다!", + "DialogUpdaterFailedToGetVersionMessage": "GitHub 릴리스에서 릴리스 정보를 가져오는 중에 오류가 발생했습니다. 이는 GitHub Actions에서 새 릴리스를 컴파일하는 경우 발생할 수 있습니다. 몇 분 후에 다시 시도하세요.", + "DialogUpdaterConvertFailedGithubMessage": "Github 개정에서 받은 Ryujinx 버전을 변환하지 못했습니다.\n", + "DialogUpdaterDownloadingMessage": "업데이트 다운로드 중...\n", + "DialogUpdaterExtractionMessage": "업데이트 추출 중...\n", + "DialogUpdaterRenamingMessage": "업데이트 이름 바꾸는 중...\n", + "DialogUpdaterAddingFilesMessage": "새 업데이트 추가 중...\n", + "DialogUpdaterCompleteMessage": "업데이트 완료하였습니다!\n", + "DialogUpdaterRestartMessage": "지금 Ryujinx를 다시 시작하겠습니까?", + "DialogUpdaterArchNotSupportedMessage": "지원되는 시스템 아키텍처를 실행하고 있지 않습니다!", + "DialogUpdaterArchNotSupportedSubMessage": "(x64 시스템만 지원됩니다!)\n", + "DialogUpdaterNoInternetMessage": "인터넷에 연결되어 있지 않습니다!", + "DialogUpdaterNoInternetSubMessage": "인터넷 연결이 작동하는지 확인하세요!", + "DialogUpdaterDirtyBuildMessage": "Ryujinx의 나쁜 빌드는 업데이트할 수 없습니다!\n", + "DialogUpdaterDirtyBuildSubMessage": "지원되는 버전을 찾고 있다면 https://ryujinx.org/에서 Ryujinx를 다운로드하세요.", + "DialogRestartRequiredMessage": "재시작 필요", + "DialogThemeRestartMessage": "테마가 저장되었습니다. 테마를 적용하려면 다시 시작해야 합니다.", + "DialogThemeRestartSubMessage": "다시 시작하겠습니까?\n", + "DialogFirmwareInstallEmbeddedMessage": "이 게임에 내장된 펌웨어를 설치하겠습니까? (펌웨어 {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "설치된 펌웨어가 없지만 Ryujinx가 제공된 게임에서 펌웨어 {0}을(를) 설치할 수 있었습니다.\\n이제 에뮬레이터가 시작됩니다.", + "DialogFirmwareNoFirmwareInstalledMessage": "설치된 펌웨어 없음", + "DialogFirmwareInstalledMessage": "펌웨어 {0}이(가) 설치됨", + "DialogOpenSettingsWindowLabel": "설정 창 열기", + "DialogControllerAppletTitle": "컨트롤러 애플릿", + "DialogMessageDialogErrorExceptionMessage": "메시지 대화상자를 표시하는 동안 오류 발생: {0}\n", + "DialogSoftwareKeyboardErrorExceptionMessage": "소프트웨어 키보드를 표시하는 동안 오류 발생: {0}", + "DialogErrorAppletErrorExceptionMessage": "오류에플릿 대화상자를 표시하는 동안 오류 발생: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\n이 오류를 수정하는 방법에 대한 자세한 내용은 설정 가이드를 따르세요.", + "DialogUserErrorDialogTitle": "Ryuijnx 오류 ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "API에서 정보를 가져오는 동안 오류가 발생했습니다.", + "DialogAmiiboApiConnectErrorMessage": "Amiibo API 서버에 연결할 수 없습니다. 서비스가 다운되었거나 인터넷 연결이 오프라인인지 확인해야 할 수 있습니다.", + "DialogProfileInvalidProfileErrorMessage": "{0} 프로파일은 현재 입력 구성 시스템과 호환되지 않습니다.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "기본 프로파일을 덮어쓸 수 없음", + "DialogProfileDeleteProfileTitle": "프로파일 삭제", + "DialogProfileDeleteProfileMessage": "이 작업은 되돌릴 수 없습니다. 계속하겠습니까?", + "DialogWarning": "경고", + "DialogPPTCDeletionMessage": "다음에 부팅할 때 PPTC 재구축을 대기시키려고 합니다:\n\n{0}\n\n계속하겠습니까?", + "DialogPPTCDeletionErrorMessage": "{0}에서 PPTC 캐시 삭제 오류: {1}", + "DialogShaderDeletionMessage": "다음에 대한 셰이더 캐시를 삭제하려고 합니다:\n\n{0}\n\n계속하겠습니까?", + "DialogShaderDeletionErrorMessage": "{0}에서 셰이더 캐시 제거 오류: {1}", + "DialogRyujinxErrorMessage": "Ryujinx에 오류 발생", + "DialogInvalidTitleIdErrorMessage": "UI 오류: 선택한 게임에 유효한 타이틀 ID가 없음", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "{0}에서 유효한 시스템 펌웨어를 찾을 수 없습니다.", + "DialogFirmwareInstallerFirmwareInstallTitle": "펌웨어 {0} 설치", + "DialogFirmwareInstallerFirmwareInstallMessage": "시스템 버전 {0}이(가) 설치됩니다.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n이것은 현재 시스템 버전 {0}을(를) 대체합니다.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n계속하겠습니까?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "펌웨어 설치 중...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "시스템 버전 {0}이(가) 성공적으로 설치되었습니다.", + "DialogUserProfileDeletionWarningMessage": "선택한 프로파일이 삭제되면 사용 가능한 다른 프로파일이 없음", + "DialogUserProfileDeletionConfirmMessage": "선택한 프로파일을 삭제하겠습니까?", + "DialogControllerSettingsModifiedConfirmMessage": "현재 컨트롤러 설정이 업데이트되었습니다.", + "DialogControllerSettingsModifiedConfirmSubMessage": "저장하겠습니까?", + "DialogLoadNcaErrorMessage": "{0}입니다. 오류 발생 파일: {1}", + "DialogDlcNoDlcErrorMessage": "지정된 파일에 선택한 타이틀에 대한 DLC가 포함되어 있지 않습니다!", + "DialogPerformanceCheckLoggingEnabledMessage": "개발자만 사용하도록 설계된 추적 로깅이 활성화되어 있습니다.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "최적의 성능을 위해 추적 로깅을 비활성화하는 것이 좋습니다. 지금 추적 로깅을 비활성화하겠습니까?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "개발자만 사용하도록 설계된 셰이더 덤프를 활성화했습니다.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "최적의 성능을 위해 세이더 덤핑을 비활성화하는 것이 좋습니다. 지금 세이더 덤핑을 비활성화하겠습니까?", + "DialogLoadAppGameAlreadyLoadedMessage": "게임을 이미 불러 왔습니다.", + "DialogLoadAppGameAlreadyLoadedSubMessage": "다른 게임을 시작하기 전에 에뮬레이션을 중지하거나 에뮬레이터를 닫으세요.", + "DialogUpdateAddUpdateErrorMessage": "지정된 파일에 선택한 제목에 대한 업데이트가 포함되어 있지 않습니다!", + "DialogSettingsBackendThreadingWarningTitle": "경고 - 백엔드 스레딩", + "DialogSettingsBackendThreadingWarningMessage": "변경 사항을 완전히 적용하려면 이 옵션을 변경한 후 Ryujinx를 다시 시작해야 합니다. 플랫폼에 따라 Ryujinx를 사용할 때 드라이버 자체의 멀티스레딩을 수동으로 비활성화해야 할 수도 있습니다.", + "SettingsTabGraphicsFeaturesOptions": "기능", + "SettingsTabGraphicsBackendMultithreading": "그래픽 백엔드 멀티스레딩:", + "CommonAuto": "자동", + "CommonOff": "끔", + "CommonOn": "켬", + "InputDialogYes": "네", + "InputDialogNo": "아니오", + "DialogProfileInvalidProfileNameErrorMessage": "파일 이름에 잘못된 문자가 포함되어 있습니다. 다시 시도하세요.", + "MenuBarOptionsPauseEmulation": "일시 정지", + "MenuBarOptionsResumeEmulation": "다시 시작", + "AboutUrlTooltipMessage": "기본 브라우저에서 Ryujinx 웹사이트를 열려면 클릭하세요.", + "AboutDisclaimerMessage": "Ryujinx는 닌텐도™,\n또는 그 파트너와 제휴하지 않습니다.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com)는\nAmiibo 에뮬레이션에 사용됩니다.", + "AboutPatreonUrlTooltipMessage": "기본 브라우저에서 Ryujinx Patreon 페이지를 열려면 클릭하세요.", + "AboutGithubUrlTooltipMessage": "기본 브라우저에서 Ryujinx GitHub 페이지를 열려면 클릭하세요.", + "AboutDiscordUrlTooltipMessage": "기본 브라우저에서 Ryujinx 디스코드 서버에 대한 초대를 열려면 클릭하세요.", + "AboutTwitterUrlTooltipMessage": "기본 브라우저에서 Ryujinx 트위터 페이지를 열려면 클릭하세요.", + "AboutRyujinxAboutTitle": "정보:", + "AboutRyujinxAboutContent": "Ryujinx는 닌텐도 스위치™용 에뮬레이터입니다.\nPatreon에서 지원해 주세요.\n모든 최신 뉴스는 트위터 또는 디스코드에서 확인하세요.\n기여에 관심이 있는 개발자는 GitHub 또는 디스코드에서 자세한 내용을 확인할 수 있습니다", + "AboutRyujinxMaintainersTitle": "유지 관리:", + "AboutRyujinxMaintainersContentTooltipMessage": "기본 브라우저에서 기여자 페이지를 열려면 클릭하세요.", + "AboutRyujinxSupprtersTitle": "Patreon에서 후원:", + "AmiiboSeriesLabel": "Amiibo 시리즈", + "AmiiboCharacterLabel": "캐릭터", + "AmiiboScanButtonLabel": "스캔", + "AmiiboOptionsShowAllLabel": "모든 Amiibo 표시", + "AmiiboOptionsUsRandomTagLabel": "해킹: 임의의 태그 UUID 사용", + "DlcManagerTableHeadingEnabledLabel": "활성화됨", + "DlcManagerTableHeadingTitleIdLabel": "타이틀 ID", + "DlcManagerTableHeadingContainerPathLabel": "컨테이너 경로", + "DlcManagerTableHeadingFullPathLabel": "전체 경로", + "DlcManagerRemoveAllButton": "모두 제거", + "DlcManagerEnableAllButton": "모두 활성화", + "DlcManagerDisableAllButton": "모두 비활성화", + "MenuBarOptionsChangeLanguage": "언어 변경", + "CommonSort": "정렬", + "CommonShowNames": "이름 표시", + "CommonFavorite": "즐겨찾기", + "OrderAscending": "오름차순", + "OrderDescending": "내림차순", + "SettingsTabGraphicsFeatures": "기능ㆍ개선 사항", + "ErrorWindowTitle": "오류 창", + "ToggleDiscordTooltip": "\"현재 재생 중인\" 디스코드 활동에 Ryujinx를 표시할지 여부 선택", + "AddGameDirBoxTooltip": "목록에 추가할 게임 디렉토리 입력", + "AddGameDirTooltip": "목록에 게임 디렉토리 추가", + "RemoveGameDirTooltip": "선택한 게임 디렉토리 제거", + "CustomThemeCheckTooltip": "GUI에 사용자 지정 Avalonia 테마를 사용하여 에뮬레이터 메뉴의 모양 변경", + "CustomThemePathTooltip": "사용자 정의 GUI 테마 경로", + "CustomThemeBrowseTooltip": "사용자 정의 GUI 테마 찾아보기", + "DockModeToggleTooltip": "도킹 모드에서는 에뮬레이트된 시스템이 도킹된 닌텐도 스위치처럼 작동합니다. 이것은 대부분의 게임에서 그래픽 충실도를 향상시킵니다. 반대로 이 기능을 비활성화하면 에뮬레이트된 시스템이 휴대용 닌텐도 스위치처럼 작동하여 그래픽 품질이 저하됩니다.\n\n도킹 모드를 사용하려는 경우 플레이어 1 컨트롤을 구성하세요. 휴대용 모드를 사용하려는 경우 휴대용 컨트롤을 구성합니다.\n\n확실하지 않으면 켜 두세요.", + "DirectKeyboardTooltip": "직접 키보드 접속 (HID) 지원합니다. 텍스트 입력 장치로 키보드에 대한 게임 접속을 제공합니다.", + "DirectMouseTooltip": "직접 마우스 접속 (HID) 지원합니다. 포인팅 장치로 마우스에 대한 게임 접속을 제공합니다.", + "RegionTooltip": "시스템 지역 변경", + "LanguageTooltip": "시스템 언어 변경", + "TimezoneTooltip": "시스템 시간대 변경", + "TimeTooltip": "시스템 시간 변경", + "VSyncToggleTooltip": "에뮬레이트된 콘솔의 수직 동기화입니다. 기본적으로 대부분의 게임에 대한 프레임 제한 장치입니다. 비활성화하면 게임이 더 빠른 속도로 실행되거나 로딩 화면이 더 오래 걸리거나 멈출 수 있습니다.\n\n게임 내에서 선호하는 핫키로 전환할 수 있습니다. 비활성화할 계획이라면 이 작업을 수행하는 것이 좋습니다.\n\n확실하지 않으면 켜 두세요.", + "PptcToggleTooltip": "게임이 불러올 때마다 번역할 필요가 없도록 번역된 JIT 기능을 저장합니다.\n\n게임을 처음 부팅한 후 끊김 현상을 줄이고 부팅 시간을 크게 단축합니다.\n\n확실하지 않으면 켜 두세요.", + "FsIntegrityToggleTooltip": "게임을 부팅할 때 손상된 파일을 확인하고 손상된 파일이 감지되면 로그에 해시 오류를 표시합니다.\n\n성능에 영향을 미치지 않으며 문제 해결에 도움이 됩니다.\n\n확실하지 않으면 켜 두세요.", + "AudioBackendTooltip": "오디오를 렌더링하는 데 사용되는 백엔드를 변경합니다.\n\nSDL2가 선호되는 반면 OpenAL 및 사운드IO는 폴백으로 사용됩니다. 더미는 소리가 나지 않습니다.\n\n확실하지 않으면 SDL2로 설정하세요.", + "MemoryManagerTooltip": "게스트 메모리가 매핑되고 접속되는 방식을 변경합니다. 에뮬레이트된 CPU 성능에 크게 영향을 미칩니다.\n\n확실하지 않은 경우 호스트 확인 안함으로 설정하십시오.", + "MemoryManagerSoftwareTooltip": "주소 변환을 위해 소프트웨어 페이지 테이블을 사용하세요. 정확도는 가장 높지만 성능은 가장 느립니다.", + "MemoryManagerHostTooltip": "호스트 주소 공간의 메모리를 직접 매핑합니다. 훨씬 빠른 JIT 컴파일 및 실행합니다.", + "MemoryManagerUnsafeTooltip": "메모리를 직접 매핑하지만 접속하기 전에 게스트 주소 공간 내의 주소를 마스킹하지 마십시오. 더 빠르지만 안전을 희생해야 합니다. 게스트 응용 프로그램은 Ryujinx의 어디에서나 메모리에 접속할 수 있으므로 이 모드에서는 신뢰할 수 있는 프로그램만 실행하세요.", + "DRamTooltip": "대체 메모리모드 레이아웃을 활용하여 스위치 개발 모델을 모방합니다.\n\n고해상도 텍스처 팩 또는 4k 해상도 모드에만 유용합니다. 성능을 향상시키지 않습니다.\n\n확실하지 않으면 꺼 두세요.", + "IgnoreMissingServicesTooltip": "구현되지 않은 호라이즌 OS 서비스를 무시합니다. 이것은 특정 게임을 부팅할 때 충돌을 우회하는 데 도움이 될 수 있습니다.\n\n확실하지 않으면 꺼 두세요.", + "GraphicsBackendThreadingTooltip": "두 번째 스레드에서 그래픽 백엔드 명령을 실행합니다.\n\n세이더 컴파일 속도를 높이고 끊김 현상을 줄이며 자체 멀티스레딩 지원 없이 GPU 드라이버의 성능을 향상시킵니다. 멀티스레딩이 있는 드라이버에서 성능이 약간 향상되었습니다.\n\n잘 모르겠으면 자동으로 설정하세요.", + "GalThreadingTooltip": "두 번째 스레드에서 그래픽 백엔드 명령을 실행합니다.\n\n세이더 컴파일 속도를 높이고 끊김 현상을 줄이며 자체 멀티스레딩 지원 없이 GPU 드라이버의 성능을 향상시킵니다. 멀티스레딩이 있는 드라이버에서 성능이 약간 향상되었습니다.\n\n잘 모르겠으면 자동으로 설정하세요.", + "ShaderCacheToggleTooltip": "후속 실행에서 끊김 현상을 줄이는 디스크 세이더 캐시를 저장합니다.\n\n확실하지 않으면 켜 두세요.", + "ResolutionScaleTooltip": "적용 가능한 렌더 타겟에 적용된 해상도 스케일", + "ResolutionScaleEntryTooltip": "1.5와 같은 부동 소수점 분해능 스케일입니다. 비통합 척도는 문제나 충돌을 일으킬 가능성이 더 큽니다.", + "AnisotropyTooltip": "이방성 필터링 수준 (게임에서 요청한 값을 사용하려면 자동으로 설정)", + "AspectRatioTooltip": "렌더러 창에 적용된 화면비입니다.", + "ShaderDumpPathTooltip": "그래픽 셰이더 덤프 경로", + "FileLogTooltip": "디스크의 로그 파일에 콘솔 로깅을 저장합니다. 성능에 영향을 미치지 않습니다.", + "StubLogTooltip": "콘솔에 스텁 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "InfoLogTooltip": "콘솔에 정보 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "WarnLogTooltip": "콘솔에 경고 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "ErrorLogTooltip": "콘솔에 오류 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "TraceLogTooltip": "콘솔에 추적 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "GuestLogTooltip": "콘솔에 게스트 로그 메시지를 인쇄합니다. 성능에 영향을 미치지 않습니다.", + "FileAccessLogTooltip": "콘솔에 파일 액세스 로그 메시지를 인쇄합니다.", + "FSAccessLogModeTooltip": "콘솔에 대한 FS 접속 로그 출력을 활성화합니다. 가능한 모드는 0-3\t\t\t\t", + "DeveloperOptionTooltip": "주의해서 사용", + "OpenGlLogLevel": "적절한 로그 수준을 활성화해야 함", + "DebugLogTooltip": "콘솔에 디버그 로그 메시지를 인쇄합니다.\n\n로그를 읽기 어렵게 만들고 에뮬레이터 성능을 악화시키므로 직원이 구체적으로 지시한 경우에만 사용하세요.", + "LoadApplicationFileTooltip": "파일 탐색기를 열어 불러올 스위치 호환 파일 선택", + "LoadApplicationFolderTooltip": "파일 탐색기를 열어 불러올 스위치 호환 압축 해제 애플리케이션 선택", + "OpenRyujinxFolderTooltip": "Ryujinx 파일 시스템 폴더 열기", + "OpenRyujinxLogsTooltip": "로그가 기록된 폴더 열기", + "ExitTooltip": "Ryujinx 종료", + "OpenSettingsTooltip": "설정 창 열기", + "OpenProfileManagerTooltip": "사용자 프로파일 관리자 창 열기", + "StopEmulationTooltip": "현재 게임의 에뮬레이션을 중지하고 게임 선택으로 돌아감", + "CheckUpdatesTooltip": "Ryujinx 업데이트 확인", + "OpenAboutTooltip": "정보 창 열기", + "GridSize": "격자 크기", + "GridSizeTooltip": "격자 항목의 크기 변경\n", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "포르투갈어 (브라질)", + "AboutRyujinxContributorsButtonHeader": "모든 기여자 보기", + "SettingsTabSystemAudioVolume": "볼륨 : ", + "AudioVolumeTooltip": "오디오 볼륨 변경", + "SettingsTabSystemEnableInternetAccess": "게스트 인터넷 접속/LAN 모드", + "EnableInternetAccessTooltip": "에뮬레이션된 애플리케이션이 인터넷에 연결되도록 허용합니다.\n\nLAN 모드가 있는 게임은 이 모드가 활성화되고 시스템이 동일한 접속 포인트에 연결된 경우 서로 연결할 수 있습니다. 여기에는 실제 콘솔도 포함됩니다.\n\n닌텐도 서버에 연결할 수 없습니다. 인터넷에 연결을 시도하는 특정 게임에서 충돌이 발생할 수 있습니다.\n\n확실하지 않으면 꺼두세요.", + "GameListContextMenuManageCheatToolTip": "치트 관리", + "GameListContextMenuManageCheat": "치트 관리", + "ControllerSettingsStickRange": "범위:", + "DialogStopEmulationTitle": "Ryujinx - 에뮬레이션 중지", + "DialogStopEmulationMessage": "에뮬레이션을 중지하겠습니까?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "오디오", + "SettingsTabNetwork": "네트워크", + "SettingsTabNetworkConnection": "네트워크 연결", + "SettingsTabCpuCache": "CPU 캐시", + "SettingsTabCpuMemory": "CPU 메모리", + "DialogUpdaterFlatpakNotSupportedMessage": "FlatHub를 통해 Ryujinx를 업데이트하세요.", + "UpdaterDisabledWarningTitle": "업데이터 비활성화입니다!", + "GameListContextMenuOpenSdModsDirectory": "엣모스피어 Mod 디렉토리 열기", + "GameListContextMenuOpenSdModsDirectoryToolTip": "응용 프로그램의 모드가 포함된 대체 SD 카드 Atmosphere 디렉토리를 엽니다. 실제 하드웨어용으로 패키징된 모드에 유용합니다.\n", + "ControllerSettingsRotate90": "시계 방향으로 90° 회전", + "IconSize": "아이콘 크기", + "IconSizeTooltip": "게임 아이콘 크기 변경", + "MenuBarOptionsShowConsole": "콘솔 표시", + "ShaderCachePurgeError": "{0}에서 셰이더 캐시를 제거하는 중 오류 발생: {1}", + "UserErrorNoKeys": "키를 찾을 수 없음", + "UserErrorNoFirmware": "펌웨어를 찾을 수 없음", + "UserErrorFirmwareParsingFailed": "펌웨어 구문 분석 오류", + "UserErrorApplicationNotFound": "응용 프로그램을 찾을 수 없음", + "UserErrorUnknown": "알 수 없는 오류", + "UserErrorUndefined": "정의되지 않은 오류", + "UserErrorNoKeysDescription": "Ryujinx가 'prod.keys' 파일을 찾을 수 없음", + "UserErrorNoFirmwareDescription": "Ryujinx가 설치된 펌웨어를 찾을 수 없음", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx가 제공된 펌웨어를 구문 분석할 수 없습니다. 일반적으로 오래된 키가 원인입니다.\n", + "UserErrorApplicationNotFoundDescription": "Ryujinx가 지정된 경로에서 유효한 응용 프로그램을 찾을 수 없습니다.\n", + "UserErrorUnknownDescription": "알 수 없는 오류가 발생했습니다!\n", + "UserErrorUndefinedDescription": "정의되지 않은 오류가 발생했습니다! 이런 일이 발생하면 안 되므로, 개발자에게 문의하세요!\n", + "OpenSetupGuideMessage": "설정 가이드 열기", + "NoUpdate": "업데이트 없음", + "TitleUpdateVersionLabel": "버전 {0} - {1}", + "RyujinxInfo": "Ryujinx - 정보", + "RyujinxConfirm": "Ryujinx - 확인", + "FileDialogAllTypes": "모든 유형", + "Never": "절대 안 함", + "SwkbdMinCharacters": "{0} 자 이상이어야 함", + "SwkbdMinRangeCharacters": "{0}-{1} 자여야 함", + "SoftwareKeyboard": "소프트웨어 키보드", + "DialogControllerAppletMessagePlayerRange": "응용 프로그램은 다음을 사용하여 {0} 플레이어를 요청합니다:\n\n유형: {1}\n\n플레이어: {2}\n\n{3} 지금 설정을 열고 입력을 재구성하거나 닫기를 누르세요.\n", + "DialogControllerAppletMessage": "애플리케이션은 다음을 사용하여 정확히 {0}명의 플레이어를 요청합니다:\n\n유형: {1}\n\n플레이어: {2}\n\n{3} 지금 설정을 열고 입력을 재구성하거나 닫기를 누르세요.\n", + "DialogControllerAppletDockModeSet": "도킹 모드가 설정되었습니다. 휴대용도 유효하지 않습니다.\n\n\n", + "UpdaterRenaming": "이전 파일 이름 바꾸는 중...", + "UpdaterRenameFailed": "업데이터가 파일 이름을 바꿀 수 없습니다: {0}", + "UpdaterAddingFiles": "새로운 파일을 추가하는 중...", + "UpdaterExtracting": "업데이트를 추출하는 중...", + "UpdaterDownloading": "업데이트 다운로드 중...", + "Game": "게임", + "Docked": "도킹됨", + "Handheld": "휴대용", + "ConnectionError": "연결 오류입니다.", + "AboutPageDeveloperListMore": "{0} 등...", + "ApiError": "API 오류입니다.", + "LoadingHeading": "{0} 로딩 중", + "CompilingPPTC": "PTC 컴파일 중", + "CompilingShaders": "세이더 컴파일", + "AllKeyboards": "모든 키보드", + "OpenFileDialogTitle": "지원되는 파일을 선택", + "OpenFolderDialogTitle": "압축을 푼 게임이 있는 폴더 선택", + "AllSupportedFormats": "지원되는 모든 형식", + "RyujinxUpdater": "Ryujinx 업데이터", + "SettingsTabHotkeys": "키보드 단축키", + "SettingsTabHotkeysHotkeys": "키보드 단축키", + "SettingsTabHotkeysToggleVsyncHotkey": "수직 동기화 전환:", + "SettingsTabHotkeysScreenshotHotkey": "스크린샷:", + "SettingsTabHotkeysShowUiHotkey": "UI 표시:", + "SettingsTabHotkeysPauseHotkey": "일시정지:", + "SettingsTabHotkeysToggleMuteHotkey": "음소거:", + "ControllerMotionTitle": "동작 제어 설정", + "ControllerRumbleTitle": "진동 설정", + "SettingsSelectThemeFileDialogTitle": "테마 파일 선택", + "SettingsXamlThemeFile": "Xaml 테마 파일", + "AvatarWindowTitle": "계정 관리 - 아바타", + "Amiibo": "Amiibo", + "Unknown": "알 수 없음", + "Usage": "사용법", + "Writable": "쓰기 가능", + "SelectDlcDialogTitle": "DLC 파일 선택", + "SelectUpdateDialogTitle": "업데이트 파일 선택", + "UserProfileWindowTitle": "사용자 프로파일 관리자", + "CheatWindowTitle": "치트 관리자", + "DlcWindowTitle": "다운로드 가능한 콘텐츠 관리자", + "UpdateWindowTitle": "타이틀 업데이트 관리자", + "CheatWindowHeading": "{0} [{1}]에 사용할 수 있는 치트", + "DlcWindowHeading": "{1} ({2})에 사용할 수 있는 {0} 다운로드 가능한 콘텐츠", + "UserProfilesEditProfile": "선택 항목 편집", + "Cancel": "취소", + "Save": "저장", + "Discard": "삭제", + "UserProfilesSetProfileImage": "프로파일 이미지 설정", + "UserProfileEmptyNameError": "이름 필요", + "UserProfileNoImageError": "프로파일 이미지를 설정해야 함", + "GameUpdateWindowHeading": "{1} ({2})에 대한 {0} 업데이트 사용 가능", + "SettingsTabHotkeysResScaleUpHotkey": "해상도 증가:", + "SettingsTabHotkeysResScaleDownHotkey": "해상도 감소:", + "UserProfilesName": "이름:", + "UserProfilesUserId": "사용자 Id:", + "SettingsTabGraphicsBackend": "그래픽 백엔드", + "SettingsTabGraphicsBackendTooltip": "사용할 그래픽 백엔드", + "SettingsEnableTextureRecompression": "텍스처 재압축 활성화", + "SettingsEnableTextureRecompressionTooltip": "VRAM 사용량을 줄이기 위해 특정 텍스처를 압축합니다.\n\n4GiB VRAM 미만의 GPU와 함께 사용하는 것이 좋습니다.\n\n확실하지 않으면 꺼 두세요.", + "SettingsTabGraphicsPreferredGpu": "선호하는 GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Vulkan 그래픽 백엔드와 함께 사용할 그래픽 카드를 선택하세요.\n\nOpenGL이 사용할 GPU에는 영향을 미치지 않습니다.\n\n확실하지 않은 경우 \"dGPU\" 플래그가 지정된 GPU로 설정하세요. 없는 경우, 그대로 두세요.\n", + "SettingsAppRequiredRestartMessage": "Ryujinx 재시작 필요", + "SettingsGpuBackendRestartMessage": "그래픽 백엔드 또는 GPU 설정이 수정되었습니다. 적용하려면 다시 시작해야 함\n", + "SettingsGpuBackendRestartSubMessage": "지금 다시 시작하겠습니까?", + "RyujinxUpdaterMessage": "Ryujinx를 최신 버전으로 업데이트하겠습니까?", + "SettingsTabHotkeysVolumeUpHotkey": "볼륨 증가:", + "SettingsTabHotkeysVolumeDownHotkey": "볼륨 감소:", + "SettingsEnableMacroHLE": "매크로 HLE 활성화", + "SettingsEnableMacroHLETooltip": "GPU 매크로 코드의 높은 수준 에뮬레이션입니다.\n\n성능이 향상되지만 일부 게임에서 그래픽 결함이 발생할 수 있습니다.\n\n확실하지 않으면 켜 두세요.", + "VolumeShort": "볼륨", + "UserProfilesManageSaves": "저장 관리", + "DeleteUserSave": "이 게임에 대한 사용자 저장을 삭제하겠습니까?", + "IrreversibleActionNote": "이 작업은 되돌릴 수 없습니다.", + "SaveManagerHeading": "{0}의 저장 관리", + "SaveManagerTitle": "저장 관리자", + "Name": "이름", + "Size": "크기", + "Search": "검색", + "UserProfilesRecoverLostAccounts": "잃어버린 계정 복구", + "Recover": "복구", + "UserProfilesRecoverHeading": "다음 계정에 대한 저장 발견" +} diff --git a/src/Ryujinx.Ava/Assets/Locales/pl_PL.json b/src/Ryujinx.Ava/Assets/Locales/pl_PL.json new file mode 100644 index 00000000..077ccc2d --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/pl_PL.json @@ -0,0 +1,614 @@ +{ + "Language": "Polski", + "MenuBarFileOpenApplet": "Otwórz Aplet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Otwórz aplet Mii Editor w trybie Indywidualnym", + "SettingsTabInputDirectMouseAccess": "Bezpośredni Dostęp do Myszy", + "SettingsTabSystemMemoryManagerMode": "Tryb Menedżera Pamięci:", + "SettingsTabSystemMemoryManagerModeSoftware": "Oprogramowania", + "SettingsTabSystemMemoryManagerModeHost": "Host (szybko)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Niesprawdzony (najszybciej, niebezpiecznie)", + "MenuBarFile": "_Plik", + "MenuBarFileOpenFromFile": "_Załaduj Aplikację z Pliku", + "MenuBarFileOpenUnpacked": "Załaduj _Rozpakowaną Grę", + "MenuBarFileOpenEmuFolder": "Otwórz Folder Ryujinx", + "MenuBarFileOpenLogsFolder": "Otwórz Folder Logów", + "MenuBarFileExit": "_Wyjdź", + "MenuBarOptions": "Opcje", + "MenuBarOptionsToggleFullscreen": "Przełącz Tryb Pełnoekranowy", + "MenuBarOptionsStartGamesInFullscreen": "Uruchamiaj Gry w Trybie Pełnoekranowym", + "MenuBarOptionsStopEmulation": "Zatrzymaj Emulację", + "MenuBarOptionsSettings": "_Ustawienia", + "MenuBarOptionsManageUserProfiles": "_Zarządzaj Profilami Użytkowników", + "MenuBarActions": "_Akcje", + "MenuBarOptionsSimulateWakeUpMessage": "Symuluj Wiadomość Budzenia", + "MenuBarActionsScanAmiibo": "Skanuj Amiibo", + "MenuBarTools": "_Narzędzia", + "MenuBarToolsInstallFirmware": "Zainstaluj Firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Zainstaluj Firmware z XCI lub ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Zainstaluj Firmware z Katalogu", + "MenuBarHelp": "Pomoc", + "MenuBarHelpCheckForUpdates": "Sprawdź Aktualizacje", + "MenuBarHelpAbout": "O Aplikacji", + "MenuSearch": "Wyszukaj...", + "GameListHeaderFavorite": "Ulub", + "GameListHeaderIcon": "Ikona", + "GameListHeaderApplication": "Nazwa", + "GameListHeaderDeveloper": "Deweloper", + "GameListHeaderVersion": "Wersja", + "GameListHeaderTimePlayed": "Czas Gry", + "GameListHeaderLastPlayed": "Ostatnio Grane", + "GameListHeaderFileExtension": "Rozsz. Pliku", + "GameListHeaderFileSize": "Rozm. Pliku", + "GameListHeaderPath": "Ścieżka", + "GameListContextMenuOpenUserSaveDirectory": "Otwórz Katalog Zapisów Użytkownika", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Otwiera katalog, który zawiera Zapis Użytkownika Aplikacji", + "GameListContextMenuOpenDeviceSaveDirectory": "Otwórz Katalog Urządzeń Użytkownika", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Otwiera katalog, który zawiera Zapis Urządzenia Aplikacji", + "GameListContextMenuOpenBcatSaveDirectory": "Otwórz Katalog BCAT Użytkownika", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Otwiera katalog, który zawiera Zapis BCAT Aplikacji", + "GameListContextMenuManageTitleUpdates": "Zarządzaj Aktualizacjami Tytułów", + "GameListContextMenuManageTitleUpdatesToolTip": "Otwiera okno zarządzania Aktualizacjami Tytułu", + "GameListContextMenuManageDlc": "Zarządzaj DLC", + "GameListContextMenuManageDlcToolTip": "Otwiera okno zarządzania DLC", + "GameListContextMenuOpenModsDirectory": "Otwórz Katalog Modów", + "GameListContextMenuOpenModsDirectoryToolTip": "Otwiera katalog zawierający Mody Aplikacji", + "GameListContextMenuCacheManagement": "Zarządzanie Cache", + "GameListContextMenuCacheManagementPurgePptc": "Dodaj Rekompilację PPTC do Kolejki", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Zainicjuj Rekompilację PPTC przy następnym uruchomieniu gry", + "GameListContextMenuCacheManagementPurgeShaderCache": "Wyczyść Cache Shaderów", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Usuwa cache shaderów aplikacji", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Otwórz Katalog PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Otwiera katalog, który zawiera cache PPTC aplikacji", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Otwórz Katalog Cache Shaderów", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Otwiera katalog, który zawiera cache shaderów aplikacji", + "GameListContextMenuExtractData": "Wyodrębnij Dane", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Wyodrębnij sekcję ExeFS z bieżącej konfiguracji aplikacji (w tym aktualizacje)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Wyodrębnij sekcję RomFS z bieżącej konfiguracji aplikacji (w tym aktualizacje)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Wyodrębnij sekcję Logo z bieżącej konfiguracji aplikacji (w tym aktualizacje)", + "StatusBarGamesLoaded": "{0}/{1} Załadowane Gry", + "StatusBarSystemVersion": "Wersja Systemu: {0}", + "Settings": "Ustawienia", + "SettingsTabGeneral": "Interfejs Użytkownika", + "SettingsTabGeneralGeneral": "Ogólne", + "SettingsTabGeneralEnableDiscordRichPresence": "Włącz Bogatą Obecność Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Sprawdź Aktualizacje przy Uruchomieniu", + "SettingsTabGeneralShowConfirmExitDialog": "Pokaż Okno Dialogowe \"Potwierdzenia Wyjścia\"", + "SettingsTabGeneralHideCursorOnIdle": "Ukryj Kursor Podczas Bezczynności", + "SettingsTabGeneralGameDirectories": "Katalogi Gier", + "SettingsTabGeneralAdd": "Dodaj", + "SettingsTabGeneralRemove": "Usuń", + "SettingsTabSystem": "System", + "SettingsTabSystemCore": "Główne", + "SettingsTabSystemSystemRegion": "Region Systemu:", + "SettingsTabSystemSystemRegionJapan": "Japonia", + "SettingsTabSystemSystemRegionUSA": "USA", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Australia", + "SettingsTabSystemSystemRegionChina": "Chiny", + "SettingsTabSystemSystemRegionKorea": "Korea", + "SettingsTabSystemSystemRegionTaiwan": "Tajwan", + "SettingsTabSystemSystemLanguage": "Język Systemu:", + "SettingsTabSystemSystemLanguageJapanese": "Japoński", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Amerykański Angielski", + "SettingsTabSystemSystemLanguageFrench": "Francuski", + "SettingsTabSystemSystemLanguageGerman": "Niemiecki", + "SettingsTabSystemSystemLanguageItalian": "Włoski", + "SettingsTabSystemSystemLanguageSpanish": "Hiszpański", + "SettingsTabSystemSystemLanguageChinese": "Chiński", + "SettingsTabSystemSystemLanguageKorean": "Koreański", + "SettingsTabSystemSystemLanguageDutch": "Holenderski", + "SettingsTabSystemSystemLanguagePortuguese": "Portugalski", + "SettingsTabSystemSystemLanguageRussian": "Rosyjski", + "SettingsTabSystemSystemLanguageTaiwanese": "Tajwański", + "SettingsTabSystemSystemLanguageBritishEnglish": "Brytyjski Angielski", + "SettingsTabSystemSystemLanguageCanadianFrench": "Kanadyjski Francuski", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Hiszpański Latynoamerykański", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chiński Uproszczony", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Chiński Tradycyjny", + "SettingsTabSystemSystemTimeZone": "Strefa Czasowa Systemu:", + "SettingsTabSystemSystemTime": "Czas Systemu:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC (Profilowany Cache Trwałych Tłumaczeń)", + "SettingsTabSystemEnableFsIntegrityChecks": "Kontrole Integralności Systemu Plików", + "SettingsTabSystemAudioBackend": "Backend Dżwięku:", + "SettingsTabSystemAudioBackendDummy": "Atrapa", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacki", + "SettingsTabSystemHacksNote": " (mogą powodować niestabilność)", + "SettingsTabSystemExpandDramSize": "Użyj alternatywnego układu pamięci (Deweloperzy)", + "SettingsTabSystemIgnoreMissingServices": "Ignoruj Brakujące Usługi", + "SettingsTabGraphics": "Grafika", + "SettingsTabGraphicsAPI": "Graficzne API", + "SettingsTabGraphicsEnableShaderCache": "Włącz Cache Shaderów", + "SettingsTabGraphicsAnisotropicFiltering": "Filtrowanie Anizotropowe:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Auto", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Skala Rozdzielczości:", + "SettingsTabGraphicsResolutionScaleCustom": "Niestandardowa (Niezalecane)", + "SettingsTabGraphicsResolutionScaleNative": "Natywna (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Współczynnik Proporcji:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Rozciągnij do Okna", + "SettingsTabGraphicsDeveloperOptions": "Opcje Programistyczne", + "SettingsTabGraphicsShaderDumpPath": "Ścieżka Zrzutu Shaderów Grafiki:", + "SettingsTabLogging": "Logowanie", + "SettingsTabLoggingLogging": "Logowanie", + "SettingsTabLoggingEnableLoggingToFile": "Włącz Logowanie do Pliku", + "SettingsTabLoggingEnableStubLogs": "Wlącz Skróty Logów", + "SettingsTabLoggingEnableInfoLogs": "Włącz Logi Informacyjne", + "SettingsTabLoggingEnableWarningLogs": "Włącz Logi Ostrzeżeń", + "SettingsTabLoggingEnableErrorLogs": "Włącz Logi Błędów", + "SettingsTabLoggingEnableTraceLogs": "Włącz Logi Śledzenia", + "SettingsTabLoggingEnableGuestLogs": "Włącz Logi Gości", + "SettingsTabLoggingEnableFsAccessLogs": "Włącz Logi Dostępu do Systemu Plików", + "SettingsTabLoggingFsGlobalAccessLogMode": "Tryb Globalnych Logów Systemu Plików:", + "SettingsTabLoggingDeveloperOptions": "Opcje programistyczne (OSTRZEŻENIE: Zmniejszą wydajność)", + "SettingsTabLoggingGraphicsBackendLogLevel": "Ilość Logów Backendu Graficznego:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Żadne", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Błędy", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Spowolnienia", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Wszystkie", + "SettingsTabLoggingEnableDebugLogs": "Włącz Logi Debugowania", + "SettingsTabInput": "Sterowanie", + "SettingsTabInputEnableDockedMode": "Tryb Zadokowany", + "SettingsTabInputDirectKeyboardAccess": "Bezpośredni Dostęp do Klawiatury", + "SettingsButtonSave": "Zapisz", + "SettingsButtonClose": "Zamknij", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Anuluj", + "SettingsButtonApply": "Zastosuj", + "ControllerSettingsPlayer": "Gracz", + "ControllerSettingsPlayer1": "Gracz 1", + "ControllerSettingsPlayer2": "Gracz 2", + "ControllerSettingsPlayer3": "Gracz 3", + "ControllerSettingsPlayer4": "Gracz 4", + "ControllerSettingsPlayer5": "Gracz 5", + "ControllerSettingsPlayer6": "Gracz 6", + "ControllerSettingsPlayer7": "Gracz 7", + "ControllerSettingsPlayer8": "Gracz 8", + "ControllerSettingsHandheld": "Przenośny", + "ControllerSettingsInputDevice": "Urządzenie Wejściowe", + "ControllerSettingsRefresh": "Odśwież", + "ControllerSettingsDeviceDisabled": "Wyłączone", + "ControllerSettingsControllerType": "Typ Kontrolera", + "ControllerSettingsControllerTypeHandheld": "Przenośny", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "Para JoyCon-ów", + "ControllerSettingsControllerTypeJoyConLeft": "Lewy JoyCon", + "ControllerSettingsControllerTypeJoyConRight": "Prawy JoyCon", + "ControllerSettingsProfile": "Profil", + "ControllerSettingsProfileDefault": "Domyślny", + "ControllerSettingsLoad": "Wczytaj", + "ControllerSettingsAdd": "Dodaj", + "ControllerSettingsRemove": "Usuń", + "ControllerSettingsButtons": "Przyciski", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Pad Kierunkowy", + "ControllerSettingsDPadUp": "Góra", + "ControllerSettingsDPadDown": "Dół", + "ControllerSettingsDPadLeft": "Lewo", + "ControllerSettingsDPadRight": "Prawo", + "ControllerSettingsLStick": "Lewa Gałka", + "ControllerSettingsLStickButton": "Przycisk", + "ControllerSettingsLStickUp": "Góra", + "ControllerSettingsLStickDown": "Dół", + "ControllerSettingsLStickLeft": "Lewo", + "ControllerSettingsLStickRight": "Prawo", + "ControllerSettingsLStickStick": "Gałka", + "ControllerSettingsLStickInvertXAxis": "Odwróć X Gałki", + "ControllerSettingsLStickInvertYAxis": "Odwróć Y Gałki", + "ControllerSettingsLStickDeadzone": "Martwa Strefa:", + "ControllerSettingsRStick": "Prawa Gałka", + "ControllerSettingsRStickButton": "Przycisk", + "ControllerSettingsRStickUp": "Góra", + "ControllerSettingsRStickDown": "Dół", + "ControllerSettingsRStickLeft": "Lewo", + "ControllerSettingsRStickRight": "Prawo", + "ControllerSettingsRStickStick": "Gałka", + "ControllerSettingsRStickInvertXAxis": "Odwróć X Gałki", + "ControllerSettingsRStickInvertYAxis": "Odwróć Y Gałki", + "ControllerSettingsRStickDeadzone": "Martwa Strefa:", + "ControllerSettingsTriggersLeft": "Lewe Triggery", + "ControllerSettingsTriggersRight": "Prawe Triggery", + "ControllerSettingsTriggersButtonsLeft": "Lewe Przyciski Triggerów", + "ControllerSettingsTriggersButtonsRight": "Prawe Przyciski Triggerów", + "ControllerSettingsTriggers": "Triggery", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Lewe Przyciski", + "ControllerSettingsExtraButtonsRight": "Prawe Przyciski", + "ControllerSettingsMisc": "Różne", + "ControllerSettingsTriggerThreshold": "Próg Triggerów:", + "ControllerSettingsMotion": "Ruch", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Użyj ruchu zgodnego z CemuHook", + "ControllerSettingsMotionControllerSlot": "Slot Kontrolera:", + "ControllerSettingsMotionMirrorInput": "Odzwierciedlaj Sterowanie", + "ControllerSettingsMotionRightJoyConSlot": "Prawy Slot JoyCon:", + "ControllerSettingsMotionServerHost": "Host Serwera:", + "ControllerSettingsMotionGyroSensitivity": "Czułość Żyroskopu:", + "ControllerSettingsMotionGyroDeadzone": "Deadzone Żyroskopu:", + "ControllerSettingsSave": "Zapisz", + "ControllerSettingsClose": "Zamknij", + "UserProfilesSelectedUserProfile": "Wybrany Profil Użytkownika:", + "UserProfilesSaveProfileName": "Zapisz Nazwę Profilu", + "UserProfilesChangeProfileImage": "Zmień Obraz Profilu", + "UserProfilesAvailableUserProfiles": "Dostępne Profile Użytkowników:", + "UserProfilesAddNewProfile": "Utwórz Profil", + "UserProfilesDelete": "Usuwać", + "UserProfilesClose": "Zamknij", + "ProfileImageSelectionTitle": "Wybór Obrazu Profilu", + "ProfileImageSelectionHeader": "Wybierz zdjęcie profilowe", + "ProfileImageSelectionNote": "Możesz zaimportować niestandardowy obraz profilu lub wybrać awatar z firmware'u systemowego", + "ProfileImageSelectionImportImage": "Importuj Plik Obrazu", + "ProfileImageSelectionSelectAvatar": "Wybierz Awatar z Firmware'u", + "InputDialogTitle": "Okno Dialogowe Wprowadzania", + "InputDialogOk": "OK", + "InputDialogCancel": "Anuluj", + "InputDialogAddNewProfileTitle": "Wybierz Nazwę Profilu", + "InputDialogAddNewProfileHeader": "Wprowadź Nazwę Profilu", + "InputDialogAddNewProfileSubtext": "(Maksymalna Długość: {0})", + "AvatarChoose": "Wybierz", + "AvatarSetBackgroundColor": "Ustaw Kolor Tła", + "AvatarClose": "Zamknij", + "ControllerSettingsLoadProfileToolTip": "Załaduj Profil", + "ControllerSettingsAddProfileToolTip": "Dodaj Profil", + "ControllerSettingsRemoveProfileToolTip": "Usuń Profil", + "ControllerSettingsSaveProfileToolTip": "Zapisz Profil", + "MenuBarFileToolsTakeScreenshot": "Zrób Zrzut Ekranu", + "MenuBarFileToolsHideUi": "Ukryj UI", + "GameListContextMenuToggleFavorite": "Przełącz Ulubione", + "GameListContextMenuToggleFavoriteToolTip": "Przełącz status Ulubionej Gry", + "SettingsTabGeneralTheme": "Motyw", + "SettingsTabGeneralThemeCustomTheme": "Ścieżka Niestandardowych Motywów", + "SettingsTabGeneralThemeBaseStyle": "Styl Podstawowy", + "SettingsTabGeneralThemeBaseStyleDark": "Ciemny", + "SettingsTabGeneralThemeBaseStyleLight": "Jasny", + "SettingsTabGeneralThemeEnableCustomTheme": "Włącz Niestandardowy Motyw", + "ButtonBrowse": "Przeglądaj", + "ControllerSettingsConfigureGeneral": "Konfiguruj", + "ControllerSettingsRumble": "Wibracje", + "ControllerSettingsRumbleStrongMultiplier": "Mocny Mnożnik Wibracji", + "ControllerSettingsRumbleWeakMultiplier": "Słaby Mnożnik Wibracji", + "DialogMessageSaveNotAvailableMessage": "Nie ma danych zapisu dla {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Czy chcesz utworzyć dane zapisu dla tej gry?", + "DialogConfirmationTitle": "Ryujinx - Potwierdzenie", + "DialogUpdaterTitle": "Ryujinx - Aktualizator", + "DialogErrorTitle": "Ryujinx - Błąd", + "DialogWarningTitle": "Ryujinx - Uwaga", + "DialogExitTitle": "Ryujinx - Wyjdź", + "DialogErrorMessage": "Ryujinx napotkał błąd", + "DialogExitMessage": "Czy na pewno chcesz zamknąć Ryujinx?", + "DialogExitSubMessage": "Wszystkie niezapisane dane zostaną utracone!", + "DialogMessageCreateSaveErrorMessage": "Wystąpił błąd podczas tworzenia określonych danych zapisu: {0}", + "DialogMessageFindSaveErrorMessage": "Wystąpił błąd podczas znajdowania określonych danych zapisu: {0}", + "FolderDialogExtractTitle": "Wybierz folder do rozpakowania", + "DialogNcaExtractionMessage": "Wyodrębnianie sekcji {0} z {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Ekstraktor Sekcji NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Niepowodzenie ekstrakcji. W wybranym pliku nie było głównego NCA.", + "DialogNcaExtractionCheckLogErrorMessage": "Niepowodzenie ekstrakcji. Przeczytaj plik dziennika, aby uzyskać więcej informacji.", + "DialogNcaExtractionSuccessMessage": "Ekstrakcja zakończona pomyślnie.", + "DialogUpdaterConvertFailedMessage": "Nie udało się przekonwertować obecnej wersji Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Anulowanie Aktualizacji!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Używasz już najnowszej wersji Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Wystąpił błąd podczas próby uzyskania informacji o wydaniu z GitHub Release. Może to być spowodowane nową wersją kompilowaną przez GitHub Actions. Spróbuj ponownie za kilka minut.", + "DialogUpdaterConvertFailedGithubMessage": "Nie udało się przekonwertować otrzymanej wersji Ryujinx z Github Release.", + "DialogUpdaterDownloadingMessage": "Pobieranie Aktualizacji...", + "DialogUpdaterExtractionMessage": "Wypakowywanie Aktualizacji...", + "DialogUpdaterRenamingMessage": "Zmiana Nazwy Aktualizacji...", + "DialogUpdaterAddingFilesMessage": "Dodawanie Nowej Aktualizacji...", + "DialogUpdaterCompleteMessage": "Aktualizacja Zakończona!", + "DialogUpdaterRestartMessage": "Czy chcesz teraz zrestartować Ryujinx?", + "DialogUpdaterArchNotSupportedMessage": "Nie używasz obsługiwanej architektury systemu!", + "DialogUpdaterArchNotSupportedSubMessage": "(Obsługiwane są tylko systemy x64!)", + "DialogUpdaterNoInternetMessage": "Nie masz połączenia z Internetem!", + "DialogUpdaterNoInternetSubMessage": "Sprawdź, czy masz działające połączenie internetowe!", + "DialogUpdaterDirtyBuildMessage": "Nie możesz zaktualizować Dirty wersji Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Pobierz Ryujinx ze strony https://ryujinx.org/, jeśli szukasz obsługiwanej wersji.", + "DialogRestartRequiredMessage": "Wymagane Ponowne Uruchomienie", + "DialogThemeRestartMessage": "Motyw został zapisany. Aby zastosować motyw, konieczne jest ponowne uruchomienie.", + "DialogThemeRestartSubMessage": "Czy chcesz uruchomić ponownie?", + "DialogFirmwareInstallEmbeddedMessage": "Czy chcesz zainstalować firmware wbudowany w tę grę? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Nie znaleziono zainstalowanego firmware'u, ale Ryujinx był w stanie zainstalować firmware {0} z dostarczonej gry.\n\nEmulator uruchomi się teraz.", + "DialogFirmwareNoFirmwareInstalledMessage": "Brak Zainstalowanego Firmware'u", + "DialogFirmwareInstalledMessage": "Firmware {0} został zainstalowany", + "DialogOpenSettingsWindowLabel": "Otwórz Okno Ustawień", + "DialogControllerAppletTitle": "Aplet Kontrolera", + "DialogMessageDialogErrorExceptionMessage": "Błąd wyświetlania okna Dialogowego Wiadomości: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Błąd wyświetlania Klawiatury Oprogramowania: {0}", + "DialogErrorAppletErrorExceptionMessage": "Błąd wyświetlania okna Dialogowego ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nAby uzyskać więcej informacji o tym, jak naprawić ten błąd, zapoznaj się z naszym Przewodnikiem instalacji.", + "DialogUserErrorDialogTitle": "Błąd Ryujinxa ({0})", + "DialogAmiiboApiTitle": "API Amiibo", + "DialogAmiiboApiFailFetchMessage": "Wystąpił błąd podczas pobierania informacji z API.", + "DialogAmiiboApiConnectErrorMessage": "Nie można połączyć się z serwerem API Amiibo. Usługa może nie działać lub może być konieczne sprawdzenie, czy połączenie internetowe jest online.", + "DialogProfileInvalidProfileErrorMessage": "Profil {0} jest niezgodny z bieżącym systemem konfiguracji sterowania.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Profil Domyślny nie może zostać nadpisany", + "DialogProfileDeleteProfileTitle": "Usuwanie Profilu", + "DialogProfileDeleteProfileMessage": "Ta czynność jest nieodwracalna, czy na pewno chcesz kontynuować?", + "DialogWarning": "Uwaga", + "DialogPPTCDeletionMessage": "Masz zamiar umieścić w kolejce rekompilację PPTC przy następnym uruchomieniu:\n\n{0}\n\nCzy na pewno chcesz kontynuować?", + "DialogPPTCDeletionErrorMessage": "Błąd czyszczenia cache PPTC w {0}: {1}", + "DialogShaderDeletionMessage": "Zamierzasz usunąć cache Shaderów dla :\n\n{0}\n\nNa pewno chcesz kontynuować?", + "DialogShaderDeletionErrorMessage": "Błąd czyszczenia cache Shaderów w {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx napotkał błąd", + "DialogInvalidTitleIdErrorMessage": "Błąd UI: Wybrana gra nie miała prawidłowego ID tytułu", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Nie znaleziono prawidłowego firmware'u systemowego w {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Zainstaluj Firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Wersja systemu {0} zostanie zainstalowana.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nZastąpi to obecną wersję systemu {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nCzy chcesz kontynuować?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Instalowanie firmware'u...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Wersja systemu {0} została pomyślnie zainstalowana.", + "DialogUserProfileDeletionWarningMessage": "Nie będzie innych profili do otwarcia, jeśli wybrany profil zostanie usunięty", + "DialogUserProfileDeletionConfirmMessage": "Czy chcesz usunąć wybrany profil", + "DialogControllerSettingsModifiedConfirmMessage": "Aktualne ustawienia kontrolera zostały zaktualizowane.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Czy chcesz zapisać?", + "DialogLoadNcaErrorMessage": "{0}. Błędny Plik: {1}", + "DialogDlcNoDlcErrorMessage": "Określony plik nie zawiera DLC dla wybranego tytułu!", + "DialogPerformanceCheckLoggingEnabledMessage": "Masz włączone rejestrowanie śledzenia, które jest przeznaczone tylko dla programistów.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Aby uzyskać optymalną wydajność, zaleca się wyłączenie rejestrowania śledzenia. Czy chcesz teraz wyłączyć rejestrowanie śledzenia?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Masz włączone zrzucanie shaderów, które jest przeznaczone tylko dla programistów.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Aby uzyskać optymalną wydajność, zaleca się wyłączenie zrzucania shaderów. Czy chcesz teraz wyłączyć zrzucanie shaderów?", + "DialogLoadAppGameAlreadyLoadedMessage": "Gra została już załadowana", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Zatrzymaj emulację lub zamknij emulator przed uruchomieniem innej gry.", + "DialogUpdateAddUpdateErrorMessage": "Określony plik nie zawiera aktualizacji dla wybranego tytułu!", + "DialogSettingsBackendThreadingWarningTitle": "Ostrzeżenie — Wątki Backend", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx musi zostać ponownie uruchomiony po zmianie tej opcji, aby działał w pełni. W zależności od platformy może być konieczne ręczne wyłączenie sterownika wielowątkowości podczas korzystania z Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Funkcje", + "SettingsTabGraphicsBackendMultithreading": "Wielowątkowość Backendu Graficznego:", + "CommonAuto": "Auto", + "CommonOff": "Wyłączone", + "CommonOn": "Włączone", + "InputDialogYes": "Tak", + "InputDialogNo": "Nie", + "DialogProfileInvalidProfileNameErrorMessage": "Nazwa pliku zawiera nieprawidłowe znaki. Proszę spróbuj ponownie.", + "MenuBarOptionsPauseEmulation": "Pauza", + "MenuBarOptionsResumeEmulation": "Wznów", + "AboutUrlTooltipMessage": "Kliknij, aby otworzyć stronę Ryujinx w domyślnej przeglądarce.", + "AboutDisclaimerMessage": "Ryujinx nie jest w żaden sposób powiązany z Nintendo™,\nani z żadnym z jej partnerów.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) jest używane\nw naszej emulacji Amiibo.", + "AboutPatreonUrlTooltipMessage": "Kliknij, aby otworzyć stronę Patreon Ryujinx w domyślnej przeglądarce.", + "AboutGithubUrlTooltipMessage": "Kliknij, aby otworzyć stronę GitHub Ryujinx w domyślnej przeglądarce.", + "AboutDiscordUrlTooltipMessage": "Kliknij, aby otworzyć zaproszenie na serwer Discord Ryujinx w domyślnej przeglądarce.", + "AboutTwitterUrlTooltipMessage": "Kliknij, aby otworzyć stronę Twitter Ryujinx w domyślnej przeglądarce.", + "AboutRyujinxAboutTitle": "O Aplikacji:", + "AboutRyujinxAboutContent": "Ryujinx to emulator Nintendo Switch™.\nWspieraj nas na Patreonie.\nOtrzymuj najnowsze wiadomości na naszym Twitterze lub Discordzie.\nDeweloperzy zainteresowani współpracą mogą dowiedzieć się więcej na naszym GitHubie lub Discordzie.", + "AboutRyujinxMaintainersTitle": "Utrzymywany Przez:", + "AboutRyujinxMaintainersContentTooltipMessage": "Kliknij, aby otworzyć stronę Współtwórcy w domyślnej przeglądarce.", + "AboutRyujinxSupprtersTitle": "Wspierani na Patreonie Przez:", + "AmiiboSeriesLabel": "Seria Amiibo", + "AmiiboCharacterLabel": "Postać", + "AmiiboScanButtonLabel": "Zeskanuj", + "AmiiboOptionsShowAllLabel": "Pokaż Wszystkie Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Hack: Użyj losowego UUID tagu", + "DlcManagerTableHeadingEnabledLabel": "Włączone", + "DlcManagerTableHeadingTitleIdLabel": "ID Tytułu", + "DlcManagerTableHeadingContainerPathLabel": "Ścieżka Kontenera", + "DlcManagerTableHeadingFullPathLabel": "Pełna Ścieżka", + "DlcManagerRemoveAllButton": "Usuń Wszystkie", + "DlcManagerEnableAllButton": "Włącz Wszystkie", + "DlcManagerDisableAllButton": "Wyłącz Wszystkie", + "MenuBarOptionsChangeLanguage": "Zmień Język", + "CommonSort": "Sortuj", + "CommonShowNames": "Pokaż Nazwy", + "CommonFavorite": "Ulubione", + "OrderAscending": "Rosnąco", + "OrderDescending": "Malejąco", + "SettingsTabGraphicsFeatures": "Funkcje i Ulepszenia", + "ErrorWindowTitle": "Okno Błędu", + "ToggleDiscordTooltip": "Wybierz, czy chcesz wyświetlać Ryujinx w swojej \"aktualnie grane\" aktywności Discord", + "AddGameDirBoxTooltip": "Wprowadź katalog gier aby dodać go do listy", + "AddGameDirTooltip": "Dodaj katalog gier do listy", + "RemoveGameDirTooltip": "Usuń wybrany katalog gier", + "CustomThemeCheckTooltip": "Użyj niestandardowego motywu Avalonia dla GUI, aby zmienić wygląd menu emulatora", + "CustomThemePathTooltip": "Ścieżka do niestandardowego motywu GUI", + "CustomThemeBrowseTooltip": "Wyszukaj niestandardowy motyw GUI", + "DockModeToggleTooltip": "Tryb Zadokowany sprawia, że emulowany system zachowuje się jak zadokowany Nintendo Switch. Poprawia to jakość grafiki w większości gier. I odwrotnie, wyłączenie tej opcji sprawi, że emulowany system będzie zachowywał się jak przenośny Nintendo Switch, zmniejszając jakość grafiki.\n\nSkonfiguruj sterowanie gracza 1, jeśli planujesz używać trybu Zadokowanego; Skonfiguruj sterowanie przenośne, jeśli planujesz używać trybu przenośnego.\n\nPozostaw WŁĄCZONY, jeśli nie masz pewności.", + "DirectKeyboardTooltip": "Obsługa bezpośredniego dostępu klawiatury (HID). Zapewnia dostęp gier do klawiatury jako urządzenia do wprowadzania tekstu.", + "DirectMouseTooltip": "Obsługa bezpośredniego dostępu myszy (HID). Zapewnia grom dostęp do myszy jako urządzenia wskazującego.", + "RegionTooltip": "Zmień Region Systemu", + "LanguageTooltip": "Zmień Język Systemu", + "TimezoneTooltip": "Zmień Strefę Czasową Systemu", + "TimeTooltip": "Zmień Czas Systemu", + "VSyncToggleTooltip": "Pionowa synchronizacja emulowanej konsoli. Zasadniczo ogranicznik klatek dla większości gier; wyłączenie jej może spowodować, że gry będą działać z większą szybkością, ekrany wczytywania wydłużą się lub nawet utkną.\n\nMoże być przełączana w grze za pomocą preferowanego skrótu klawiszowego. Zalecamy to zrobić, jeśli planujesz ją wyłączyć.\n\nW razie wątpliwości pozostaw WŁĄCZONĄ", + "PptcToggleTooltip": "Zapisuje przetłumaczone funkcje JIT, dzięki czemu nie muszą być tłumaczone za każdym razem, gdy gra się ładuje.\n\nZmniejsza zacinanie się i znacznie przyspiesza uruchamianie po pierwszym uruchomieniu gry.\n\nJeśli nie masz pewności, pozostaw WŁĄCZONE", + "FsIntegrityToggleTooltip": "Sprawdza pliki podczas uruchamiania gry i jeśli zostaną wykryte uszkodzone pliki, wyświetla w dzienniku błąd hash.\n\nNie ma wpływu na wydajność i ma pomóc w rozwiązywaniu problemów.\n\nPozostaw WŁĄCZONE, jeśli nie masz pewności.", + "AudioBackendTooltip": "Zmienia backend używany do renderowania dźwięku.\n\nSDL2 jest preferowany, podczas gdy OpenAL i SoundIO są używane jako rezerwy. Dummy nie będzie odtwarzać dźwięku.\n\nW razie wątpliwości ustaw SDL2.", + "MemoryManagerTooltip": "Zmień sposób mapowania i uzyskiwania dostępu do pamięci gości. Znacznie wpływa na wydajność emulowanego procesora.\n\nUstaw na HOST UNCHECKED, jeśli nie masz pewności.", + "MemoryManagerSoftwareTooltip": "Użyj tabeli stron oprogramowania do translacji adresów. Najwyższa celność, ale najwolniejsza wydajność.", + "MemoryManagerHostTooltip": "Bezpośrednio mapuj pamięć w przestrzeni adresowej hosta. Znacznie szybsza kompilacja i wykonanie JIT.", + "MemoryManagerUnsafeTooltip": "Bezpośrednio mapuj pamięć, ale nie maskuj adresu w przestrzeni adresowej gościa przed uzyskaniem dostępu. Szybciej, ale kosztem bezpieczeństwa. Aplikacja gościa może uzyskać dostęp do pamięci z dowolnego miejsca w Ryujinx, więc w tym trybie uruchamiaj tylko programy, którym ufasz.", + "DRamTooltip": "Wykorzystuje alternatywny układ MemoryMode, aby naśladować model rozwojowy Switcha.\n\nJest to przydatne tylko w przypadku pakietów tekstur o wyższej rozdzielczości lub modów w rozdzielczości 4k. NIE poprawia wydajności.\n\nW razie wątpliwości pozostaw WYŁĄCZONE.", + "IgnoreMissingServicesTooltip": "Ignoruje niezaimplementowane usługi Horizon OS. Może to pomóc w ominięciu awarii podczas uruchamiania niektórych gier.\n\nW razie wątpliwości pozostaw WYŁĄCZONE.", + "GraphicsBackendThreadingTooltip": "Wykonuje polecenia backend'u graficznego w drugim wątku.\n\nPrzyspiesza kompilację shaderów, zmniejsza zacinanie się i poprawia wydajność sterowników GPU bez własnej obsługi wielowątkowości. Nieco lepsza wydajność w sterownikach z wielowątkowością.\n\nUstaw na AUTO, jeśli nie masz pewności.", + "GalThreadingTooltip": "Wykonuje polecenia backend'u graficznego w drugim wątku.\n\nPrzyspiesza kompilację shaderów, zmniejsza zacinanie się i poprawia wydajność sterowników GPU bez własnej obsługi wielowątkowości. Nieco lepsza wydajność w sterownikach z wielowątkowością.\n\nUstaw na AUTO, jeśli nie masz pewności.", + "ShaderCacheToggleTooltip": "Zapisuje pamięć podręczną shaderów na dysku, co zmniejsza zacinanie się w kolejnych uruchomieniach.\n\nPozostaw WŁĄCZONE, jeśli nie masz pewności.", + "ResolutionScaleTooltip": "Skala Rozdzielczości zastosowana do odpowiednich celów renderowania", + "ResolutionScaleEntryTooltip": "Skala rozdzielczości zmiennoprzecinkowej, np. 1,5. Skale niecałkowite częściej powodują problemy lub awarie.", + "AnisotropyTooltip": "Poziom filtrowania anizotropowego (ustaw na Auto, aby użyć wartości wymaganej przez grę)", + "AspectRatioTooltip": "Współczynnik proporcji zastosowany do okna renderowania.", + "ShaderDumpPathTooltip": "Ścieżka Zrzutu Shaderów Grafiki", + "FileLogTooltip": "Zapisuje logowanie konsoli w pliku dziennika na dysku. Nie wpływa na wydajność.", + "StubLogTooltip": "Wyświetla w konsoli skrótowe komunikaty dziennika. Nie wpływa na wydajność.", + "InfoLogTooltip": "Wyświetla komunikaty dziennika informacyjnego w konsoli. Nie wpływa na wydajność.", + "WarnLogTooltip": "Wyświetla komunikaty dziennika ostrzeżeń w konsoli. Nie wpływa na wydajność.", + "ErrorLogTooltip": "Wyświetla w konsoli komunikaty dziennika błędów. Nie wpływa na wydajność.", + "TraceLogTooltip": "Wyświetla komunikaty dziennika śledzenia w konsoli. Nie wpływa na wydajność.", + "GuestLogTooltip": "Wyświetla komunikaty dziennika gości w konsoli. Nie wpływa na wydajność.", + "FileAccessLogTooltip": "Wyświetla w konsoli komunikaty dziennika dostępu do plików.", + "FSAccessLogModeTooltip": "Włącza wyjście dziennika dostępu FS do konsoli. Możliwe tryby to 0-3", + "DeveloperOptionTooltip": "Używaj ostrożnie", + "OpenGlLogLevel": "Wymaga włączonych odpowiednich poziomów logów", + "DebugLogTooltip": "Wyświetla komunikaty dziennika debugowania w konsoli.\n\nUżywaj tego tylko na wyraźne polecenie członka załogi, ponieważ utrudni to odczytanie dzienników i pogorszy wydajność emulatora.", + "LoadApplicationFileTooltip": "Otwórz eksplorator plików, aby wybrać plik kompatybilny z Switch do wczytania", + "LoadApplicationFolderTooltip": "Otwórz eksplorator plików, aby wybrać zgodną z Switch, rozpakowaną aplikację do załadowania", + "OpenRyujinxFolderTooltip": "Otwórz folder systemu plików Ryujinx", + "OpenRyujinxLogsTooltip": "Otwiera folder, w którym zapisywane są logi", + "ExitTooltip": "Wyjdź z Ryujinx", + "OpenSettingsTooltip": "Otwórz okno ustawień", + "OpenProfileManagerTooltip": "Otwórz okno Menedżera Profili Użytkownika", + "StopEmulationTooltip": "Zatrzymaj emulację bieżącej gry i wróć do wyboru gier", + "CheckUpdatesTooltip": "Sprawdź aktualizacje Ryujinx", + "OpenAboutTooltip": "Otwórz Okno Informacje", + "GridSize": "Wielkość siatki", + "GridSizeTooltip": "Zmień rozmiar elementów siatki", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brazylijski Portugalski", + "AboutRyujinxContributorsButtonHeader": "Zobacz Wszystkich Współtwórców", + "SettingsTabSystemAudioVolume": "Głośność: ", + "AudioVolumeTooltip": "Zmień Głośność Dźwięku", + "SettingsTabSystemEnableInternetAccess": "Dostęp do Internetu Gościa/Tryb LAN", + "EnableInternetAccessTooltip": "Pozwala emulowanej aplikacji na łączenie się z Internetem.\n\nGry w trybie LAN mogą łączyć się ze sobą, gdy ta opcja jest włączona, a systemy są połączone z tym samym punktem dostępu. Dotyczy to również prawdziwych konsol.\n\nNie pozwala na łączenie się z serwerami Nintendo. Może powodować awarie niektórych gier, które próbują połączyć się z Internetem.\n\nPozostaw WYŁĄCZONE, jeśli nie masz pewności.", + "GameListContextMenuManageCheatToolTip": "Zarządzaj Kodami", + "GameListContextMenuManageCheat": "Zarządzaj Kodami", + "ControllerSettingsStickRange": "Zasięg:", + "DialogStopEmulationTitle": "Ryujinx - Zatrzymaj Emulację", + "DialogStopEmulationMessage": "Czy na pewno chcesz zatrzymać emulację?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Dżwięk", + "SettingsTabNetwork": "Sieć", + "SettingsTabNetworkConnection": "Połączenie Sieciowe", + "SettingsTabCpuCache": "Cache CPU", + "SettingsTabCpuMemory": "Pamięć CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Zaktualizuj Ryujinx przez FlatHub.", + "UpdaterDisabledWarningTitle": "Aktualizator Wyłączony!", + "GameListContextMenuOpenSdModsDirectory": "Otwórz Katalog Modów Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Otwiera alternatywny katalog Atmosphere na karcie SD, który zawiera modyfikacje aplikacji. Przydatne dla modów, które są pakowane dla prawdziwego sprzętu.", + "ControllerSettingsRotate90": "Obróć o 90° w Prawo", + "IconSize": "Rozmiar Ikon", + "IconSizeTooltip": "Zmień rozmiar ikon gry", + "MenuBarOptionsShowConsole": "Pokaż Konsolę", + "ShaderCachePurgeError": "Błąd podczas czyszczenia cache shaderów w {0}: {1}", + "UserErrorNoKeys": "Nie znaleziono kluczy", + "UserErrorNoFirmware": "Nie znaleziono firmware'u", + "UserErrorFirmwareParsingFailed": "Błąd parsowania firmware'u", + "UserErrorApplicationNotFound": "Aplikacja nie znaleziona", + "UserErrorUnknown": "Nieznany błąd", + "UserErrorUndefined": "Niezdefiniowany błąd", + "UserErrorNoKeysDescription": "Ryujinx nie mógł znaleźć twojego pliku 'prod.keys'", + "UserErrorNoFirmwareDescription": "Ryujinx nie mógł znaleźć żadnego zainstalowanego firmware'u", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx nie był w stanie zparsować dostarczonego firmware'u. Jest to zwykle spowodowane nieaktualnymi kluczami.", + "UserErrorApplicationNotFoundDescription": "Ryujinx nie mógł znaleźć prawidłowej aplikacji na podanej ścieżce.", + "UserErrorUnknownDescription": "Wystąpił nieznany błąd!", + "UserErrorUndefinedDescription": "Wystąpił niezdefiniowany błąd! To nie powinno się zdarzyć, skontaktuj się z deweloperem!", + "OpenSetupGuideMessage": "Otwórz Podręcznik Konfiguracji", + "NoUpdate": "Brak Aktualizacji", + "TitleUpdateVersionLabel": "Wersja {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Potwierdzenie", + "FileDialogAllTypes": "Wszystkie typy", + "Never": "Nigdy", + "SwkbdMinCharacters": "Musi mieć co najmniej {0} znaków", + "SwkbdMinRangeCharacters": "Musi mieć długość od {0}-{1} znaków", + "SoftwareKeyboard": "Klawiatura Oprogramowania", + "DialogControllerAppletMessagePlayerRange": "Aplikacja żąda {0} graczy z:\n\nTYPY: {1}\n\nGRACZE: {2}\n\n{3}Otwórz Ustawienia i ponownie skonfiguruj Sterowanie lub naciśnij Zamknij.", + "DialogControllerAppletMessage": "Aplikacja żąda dokładnie {0} graczy z:\n\nTYPY: {1}\n\nGRACZE: {2}\n\n{3}Otwórz teraz Ustawienia i ponownie skonfiguruj Sterowanie lub naciśnij Zamknij.", + "DialogControllerAppletDockModeSet": "Ustawiono tryb Zadokowane. Przenośny też jest nieprawidłowy.\n\n", + "UpdaterRenaming": "Zmienianie Nazw Starych Plików...", + "UpdaterRenameFailed": "Aktualizator nie mógł zmienić nazwy pliku: {0}", + "UpdaterAddingFiles": "Dodawanie Nowych Plików...", + "UpdaterExtracting": "Wypakowywanie Aktualizacji...", + "UpdaterDownloading": "Pobieranie Aktualizacji...", + "Game": "Gra", + "Docked": "Zadokowany", + "Handheld": "Przenośny", + "ConnectionError": "Błąd Połączenia.", + "AboutPageDeveloperListMore": "{0} i więcej...", + "ApiError": "Błąd API.", + "LoadingHeading": "Wczytywanie {0}", + "CompilingPPTC": "Kompilowanie PTC", + "CompilingShaders": "Kompilowanie Shaderów", + "AllKeyboards": "Wszystkie klawiatury", + "OpenFileDialogTitle": "Wybierz obsługiwany plik do otwarcia", + "OpenFolderDialogTitle": "Wybierz folder z rozpakowaną grą", + "AllSupportedFormats": "Wszystkie Obsługiwane Formaty", + "RyujinxUpdater": "Aktualizator Ryujinx", + "SettingsTabHotkeys": "Skróty Klawiszowe Klawiatury", + "SettingsTabHotkeysHotkeys": "Skróty Klawiszowe Klawiatury", + "SettingsTabHotkeysToggleVsyncHotkey": "Przełącz VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Zrzut Ekranu:", + "SettingsTabHotkeysShowUiHotkey": "Pokaż UI:", + "SettingsTabHotkeysPauseHotkey": "Pauza:", + "SettingsTabHotkeysToggleMuteHotkey": "Wycisz:", + "ControllerMotionTitle": "Ustawienia Sterowania Ruchowego", + "ControllerRumbleTitle": "Ustawienia Wibracji", + "SettingsSelectThemeFileDialogTitle": "Wybierz Plik Motywu", + "SettingsXamlThemeFile": "Plik Motywu Xaml", + "AvatarWindowTitle": "Zarządzaj Kontami — Avatar", + "Amiibo": "Amiibo", + "Unknown": "Nieznane", + "Usage": "Użycie", + "Writable": "Zapisywalne", + "SelectDlcDialogTitle": "Wybierz pliki DLC", + "SelectUpdateDialogTitle": "Wybierz pliki aktualizacji", + "UserProfileWindowTitle": "Menedżer Profili Użytkowników", + "CheatWindowTitle": "Menedżer Kodów", + "DlcWindowTitle": "Menedżer Zawartości do Pobrania", + "UpdateWindowTitle": "Menedżer Aktualizacji Tytułu", + "CheatWindowHeading": "Kody Dostępne dla {0} [{1}]", + "DlcWindowHeading": "{0} Zawartości do Pobrania dostępna dla {1} ({2})", + "UserProfilesEditProfile": "Edytuj Zaznaczone", + "Cancel": "Anuluj", + "Save": "Zapisz", + "Discard": "Odrzuć", + "UserProfilesSetProfileImage": "Ustaw Obraz Profilu", + "UserProfileEmptyNameError": "Nazwa jest wymagana", + "UserProfileNoImageError": "Należy ustawić obraz profilowy", + "GameUpdateWindowHeading": "{0} Aktualizacje dostępne dla {1} ({2})", + "SettingsTabHotkeysResScaleUpHotkey": "Zwiększ Rozdzielczość:", + "SettingsTabHotkeysResScaleDownHotkey": "Zmniejsz Rozdzielczość:", + "UserProfilesName": "Nazwa:", + "UserProfilesUserId": "ID Użytkownika:", + "SettingsTabGraphicsBackend": "Backend Graficzny", + "SettingsTabGraphicsBackendTooltip": "Używalne Backendy Graficzne", + "SettingsEnableTextureRecompression": "Włącz Rekompresję Tekstur", + "SettingsEnableTextureRecompressionTooltip": "Kompresuje niektóre tekstury w celu zmniejszenia zużycia pamięci VRAM.\n\nZalecane do użytku z GPU, które mają mniej niż 4 GiB pamięci VRAM.\n\nW razie wątpliwości pozostaw WYŁĄCZONE.", + "SettingsTabGraphicsPreferredGpu": "Preferowane GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Wybierz kartę graficzną, która będzie używana z backendem graficznym Vulkan.\n\nNie wpływa na GPU używane przez OpenGL.\n\nW razie wątpliwości ustaw flagę GPU jako \"dGPU\". Jeśli żadnej nie ma, pozostaw nietknięte.", + "SettingsAppRequiredRestartMessage": "Wymagane Zrestartowanie Ryujinx", + "SettingsGpuBackendRestartMessage": "Zmieniono ustawienia Backendu Graficznego lub GPU. Będzie to wymagało ponownego uruchomienia", + "SettingsGpuBackendRestartSubMessage": "Czy chcesz zrestartować teraz?", + "RyujinxUpdaterMessage": "Czy chcesz zaktualizować Ryujinx do najnowszej wersji?", + "SettingsTabHotkeysVolumeUpHotkey": "Zwiększ Głośność:", + "SettingsTabHotkeysVolumeDownHotkey": "Zmniejsz Głośność:", + "SettingsEnableMacroHLE": "Włącz Macro HLE", + "SettingsEnableMacroHLETooltip": "Wysokopoziomowa emulacja kodu GPU Macro.\n\nPoprawia wydajność, ale może powodować błędy graficzne w niektórych grach.\n\nW razie wątpliwości pozostaw WŁĄCZONE.", + "VolumeShort": "Głoś", + "UserProfilesManageSaves": "Zarządzaj Zapisami", + "DeleteUserSave": "Czy chcesz usunąć zapis użytkownika dla tej gry?", + "IrreversibleActionNote": "Ta czynność nie jest odwracalna.", + "SaveManagerHeading": "Zarządzaj Zapisami dla {0}", + "SaveManagerTitle": "Menedżer Zapisów", + "Name": "Nazwa", + "Size": "Rozmiar", + "Search": "Wyszukaj", + "UserProfilesRecoverLostAccounts": "Odzyskaj Utracone Konta", + "Recover": "Odzyskaj", + "UserProfilesRecoverHeading": "Znaleziono zapisy dla następujących kont" +} diff --git a/src/Ryujinx.Ava/Assets/Locales/pt_BR.json b/src/Ryujinx.Ava/Assets/Locales/pt_BR.json new file mode 100644 index 00000000..effe8c02 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/pt_BR.json @@ -0,0 +1,614 @@ +{ + "Language": "Português (BR)", + "MenuBarFileOpenApplet": "Abrir Applet", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Abrir editor Mii em modo avulso", + "SettingsTabInputDirectMouseAccess": "Acesso direto ao mouse", + "SettingsTabSystemMemoryManagerMode": "Modo de gerenciamento de memória:", + "SettingsTabSystemMemoryManagerModeSoftware": "Software", + "SettingsTabSystemMemoryManagerModeHost": "Hóspede (rápido)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Hóspede sem verificação (mais rápido, inseguro)", + "MenuBarFile": "_Arquivo", + "MenuBarFileOpenFromFile": "_Abrir ROM do jogo...", + "MenuBarFileOpenUnpacked": "Abrir jogo _extraído...", + "MenuBarFileOpenEmuFolder": "Abrir diretório do e_mulador...", + "MenuBarFileOpenLogsFolder": "Abrir diretório de _logs...", + "MenuBarFileExit": "_Sair", + "MenuBarOptions": "_Opções", + "MenuBarOptionsToggleFullscreen": "_Mudar para tela cheia", + "MenuBarOptionsStartGamesInFullscreen": "Iniciar jogos em tela cheia", + "MenuBarOptionsStopEmulation": "_Encerrar emulação", + "MenuBarOptionsSettings": "_Configurações", + "MenuBarOptionsManageUserProfiles": "_Gerenciar perfis de usuário", + "MenuBarActions": "_Ações", + "MenuBarOptionsSimulateWakeUpMessage": "_Simular mensagem de acordar console", + "MenuBarActionsScanAmiibo": "Escanear um Amiibo", + "MenuBarTools": "_Ferramentas", + "MenuBarToolsInstallFirmware": "_Instalar firmware", + "MenuBarFileToolsInstallFirmwareFromFile": "Instalar firmware a partir de um arquivo ZIP/XCI", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Instalar firmware a partir de um diretório", + "MenuBarHelp": "A_juda", + "MenuBarHelpCheckForUpdates": "_Verificar se há atualizações", + "MenuBarHelpAbout": "_Sobre", + "MenuSearch": "Buscar...", + "GameListHeaderFavorite": "Favorito", + "GameListHeaderIcon": "Ícone", + "GameListHeaderApplication": "Nome", + "GameListHeaderDeveloper": "Desenvolvedor", + "GameListHeaderVersion": "Versão", + "GameListHeaderTimePlayed": "Tempo de jogo", + "GameListHeaderLastPlayed": "Último jogo", + "GameListHeaderFileExtension": "Extensão", + "GameListHeaderFileSize": "Tamanho", + "GameListHeaderPath": "Caminho", + "GameListContextMenuOpenUserSaveDirectory": "Abrir diretório de saves do usuário", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Abre o diretório que contém jogos salvos para o usuário atual", + "GameListContextMenuOpenDeviceSaveDirectory": "Abrir diretório de saves de dispositivo do usuário", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Abre o diretório que contém saves do dispositivo para o usuário atual", + "GameListContextMenuOpenBcatSaveDirectory": "Abrir diretório de saves BCAT do usuário", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Abre o diretório que contém saves BCAT para o usuário atual", + "GameListContextMenuManageTitleUpdates": "Gerenciar atualizações do jogo", + "GameListContextMenuManageTitleUpdatesToolTip": "Abre a janela de gerenciamento de atualizações", + "GameListContextMenuManageDlc": "Gerenciar DLCs", + "GameListContextMenuManageDlcToolTip": "Abre a janela de gerenciamento de DLCs", + "GameListContextMenuOpenModsDirectory": "Abrir diretório de mods", + "GameListContextMenuOpenModsDirectoryToolTip": "Abre o diretório que contém modificações (mods) do jogo", + "GameListContextMenuCacheManagement": "Gerenciamento de cache", + "GameListContextMenuCacheManagementPurgePptc": "Limpar cache PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Deleta o cache PPTC armazenado em disco do jogo", + "GameListContextMenuCacheManagementPurgeShaderCache": "Limpar cache de Shader", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Deleta o cache de Shader armazenado em disco do jogo", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Abrir diretório do cache PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Abre o diretório contendo os arquivos do cache PPTC", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Abrir diretório do cache de Shader", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Abre o diretório contendo os arquivos do cache de Shader", + "GameListContextMenuExtractData": "Extrair dados", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Extrai a seção ExeFS do jogo (incluindo atualizações)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Extrai a seção RomFS do jogo (incluindo atualizações)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Extrai a seção Logo do jogo (incluindo atualizações)", + "StatusBarGamesLoaded": "{0}/{1} jogos carregados", + "StatusBarSystemVersion": "Versão do firmware: {0}", + "Settings": "Configurações", + "SettingsTabGeneral": "Geral", + "SettingsTabGeneralGeneral": "Geral", + "SettingsTabGeneralEnableDiscordRichPresence": "Habilitar Rich Presence do Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Verificar se há atualizações ao iniciar", + "SettingsTabGeneralShowConfirmExitDialog": "Exibir diálogo de confirmação ao sair", + "SettingsTabGeneralHideCursorOnIdle": "Esconder o cursor quando ocioso", + "SettingsTabGeneralGameDirectories": "Diretórios de jogo", + "SettingsTabGeneralAdd": "Adicionar", + "SettingsTabGeneralRemove": "Remover", + "SettingsTabSystem": "Sistema", + "SettingsTabSystemCore": "Principal", + "SettingsTabSystemSystemRegion": "Região do sistema:", + "SettingsTabSystemSystemRegionJapan": "Japão", + "SettingsTabSystemSystemRegionUSA": "EUA", + "SettingsTabSystemSystemRegionEurope": "Europa", + "SettingsTabSystemSystemRegionAustralia": "Austrália", + "SettingsTabSystemSystemRegionChina": "China", + "SettingsTabSystemSystemRegionKorea": "Coreia", + "SettingsTabSystemSystemRegionTaiwan": "Taiwan", + "SettingsTabSystemSystemLanguage": "Idioma do sistema:", + "SettingsTabSystemSystemLanguageJapanese": "Japonês", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Inglês americano", + "SettingsTabSystemSystemLanguageFrench": "Francês", + "SettingsTabSystemSystemLanguageGerman": "Alemão", + "SettingsTabSystemSystemLanguageItalian": "Italiano", + "SettingsTabSystemSystemLanguageSpanish": "Espanhol", + "SettingsTabSystemSystemLanguageChinese": "Chinês", + "SettingsTabSystemSystemLanguageKorean": "Coreano", + "SettingsTabSystemSystemLanguageDutch": "Holandês", + "SettingsTabSystemSystemLanguagePortuguese": "Português", + "SettingsTabSystemSystemLanguageRussian": "Russo", + "SettingsTabSystemSystemLanguageTaiwanese": "Taiwanês", + "SettingsTabSystemSystemLanguageBritishEnglish": "Inglês britânico", + "SettingsTabSystemSystemLanguageCanadianFrench": "Francês canadense", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Espanhol latino", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Chinês simplificado", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Chinês tradicional", + "SettingsTabSystemSystemTimeZone": "Fuso horário do sistema:", + "SettingsTabSystemSystemTime": "Hora do sistema:", + "SettingsTabSystemEnableVsync": "Habilitar sincronia vertical", + "SettingsTabSystemEnablePptc": "Habilitar PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Habilitar verificação de integridade do sistema de arquivos", + "SettingsTabSystemAudioBackend": "Biblioteca de saída de áudio:", + "SettingsTabSystemAudioBackendDummy": "Nenhuma", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hacks", + "SettingsTabSystemHacksNote": " (Pode causar instabilidade)", + "SettingsTabSystemExpandDramSize": "Expandir memória para 6GiB", + "SettingsTabSystemIgnoreMissingServices": "Ignorar serviços não implementados", + "SettingsTabGraphics": "Gráficos", + "SettingsTabGraphicsAPI": "API gráfica", + "SettingsTabGraphicsEnableShaderCache": "Habilitar cache de shader", + "SettingsTabGraphicsAnisotropicFiltering": "Filtragem anisotrópica:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Automático", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Escala de resolução:", + "SettingsTabGraphicsResolutionScaleCustom": "Customizada (não recomendado)", + "SettingsTabGraphicsResolutionScaleNative": "Nativa (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Proporção:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Esticar até caber", + "SettingsTabGraphicsDeveloperOptions": "Opções do desenvolvedor", + "SettingsTabGraphicsShaderDumpPath": "Diretório para despejo de shaders:", + "SettingsTabLogging": "Log", + "SettingsTabLoggingLogging": "Log", + "SettingsTabLoggingEnableLoggingToFile": "Salvar logs em arquivo", + "SettingsTabLoggingEnableStubLogs": "Habilitar logs de stub", + "SettingsTabLoggingEnableInfoLogs": "Habilitar logs de informação", + "SettingsTabLoggingEnableWarningLogs": "Habilitar logs de alerta", + "SettingsTabLoggingEnableErrorLogs": "Habilitar logs de erro", + "SettingsTabLoggingEnableTraceLogs": "Habilitar logs de rastreamento", + "SettingsTabLoggingEnableGuestLogs": "Habilitar logs do programa convidado", + "SettingsTabLoggingEnableFsAccessLogs": "Habilitar logs de acesso ao sistema de arquivos", + "SettingsTabLoggingFsGlobalAccessLogMode": "Modo global de logs do sistema de arquivos:", + "SettingsTabLoggingDeveloperOptions": "Opções do desenvolvedor (AVISO: Vai reduzir a performance)", + "SettingsTabLoggingGraphicsBackendLogLevel": "Nível de log do backend gráfico:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Nenhum", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Erro", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Lentidão", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Todos", + "SettingsTabLoggingEnableDebugLogs": "Habilitar logs de depuração", + "SettingsTabInput": "Controle", + "SettingsTabInputEnableDockedMode": "Habilitar modo TV", + "SettingsTabInputDirectKeyboardAccess": "Acesso direto ao teclado", + "SettingsButtonSave": "Salvar", + "SettingsButtonClose": "Fechar", + "SettingsButtonOk": "OK", + "SettingsButtonCancel": "Cancelar", + "SettingsButtonApply": "Aplicar", + "ControllerSettingsPlayer": "Jogador", + "ControllerSettingsPlayer1": "Jogador 1", + "ControllerSettingsPlayer2": "Jogador 2", + "ControllerSettingsPlayer3": "Jogador 3", + "ControllerSettingsPlayer4": "Jogador 4", + "ControllerSettingsPlayer5": "Jogador 5", + "ControllerSettingsPlayer6": "Jogador 6", + "ControllerSettingsPlayer7": "Jogador 7", + "ControllerSettingsPlayer8": "Jogador 8", + "ControllerSettingsHandheld": "Portátil", + "ControllerSettingsInputDevice": "Dispositivo de entrada", + "ControllerSettingsRefresh": "Atualizar", + "ControllerSettingsDeviceDisabled": "Desabilitado", + "ControllerSettingsControllerType": "Tipo do controle", + "ControllerSettingsControllerTypeHandheld": "Portátil", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "Par de JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon esquerdo", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon direito", + "ControllerSettingsProfile": "Perfil", + "ControllerSettingsProfileDefault": "Padrão", + "ControllerSettingsLoad": "Carregar", + "ControllerSettingsAdd": "Adicionar", + "ControllerSettingsRemove": "Remover", + "ControllerSettingsButtons": "Botões", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Direcional", + "ControllerSettingsDPadUp": "Cima", + "ControllerSettingsDPadDown": "Baixo", + "ControllerSettingsDPadLeft": "Esquerda", + "ControllerSettingsDPadRight": "Direita", + "ControllerSettingsLStick": "Analógico esquerdo", + "ControllerSettingsLStickButton": "Botão", + "ControllerSettingsLStickUp": "Cima", + "ControllerSettingsLStickDown": "Baixo", + "ControllerSettingsLStickLeft": "Esquerda", + "ControllerSettingsLStickRight": "Direita", + "ControllerSettingsLStickStick": "Analógico", + "ControllerSettingsLStickInvertXAxis": "Inverter eixo X", + "ControllerSettingsLStickInvertYAxis": "Inverter eixo Y", + "ControllerSettingsLStickDeadzone": "Zona morta:", + "ControllerSettingsRStick": "Analógico direito", + "ControllerSettingsRStickButton": "Botão", + "ControllerSettingsRStickUp": "Cima", + "ControllerSettingsRStickDown": "Baixo", + "ControllerSettingsRStickLeft": "Esquerda", + "ControllerSettingsRStickRight": "Direita", + "ControllerSettingsRStickStick": "Analógico", + "ControllerSettingsRStickInvertXAxis": "Inverter eixo X", + "ControllerSettingsRStickInvertYAxis": "Inverter eixo Y", + "ControllerSettingsRStickDeadzone": "Zona morta:", + "ControllerSettingsTriggersLeft": "Gatilhos esquerda", + "ControllerSettingsTriggersRight": "Gatilhos direita", + "ControllerSettingsTriggersButtonsLeft": "Botões de gatilho esquerda", + "ControllerSettingsTriggersButtonsRight": "Botões de gatilho direita", + "ControllerSettingsTriggers": "Gatilhos", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Botões esquerda", + "ControllerSettingsExtraButtonsRight": "Botões direita", + "ControllerSettingsMisc": "Miscelâneas", + "ControllerSettingsTriggerThreshold": "Sensibilidade do gatilho:", + "ControllerSettingsMotion": "Sensor de movimento", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Usar sensor compatível com CemuHook", + "ControllerSettingsMotionControllerSlot": "Slot do controle:", + "ControllerSettingsMotionMirrorInput": "Espelhar movimento", + "ControllerSettingsMotionRightJoyConSlot": "Slot do JoyCon direito:", + "ControllerSettingsMotionServerHost": "Endereço do servidor:", + "ControllerSettingsMotionGyroSensitivity": "Sensibilidade do giroscópio:", + "ControllerSettingsMotionGyroDeadzone": "Zona morta do giroscópio:", + "ControllerSettingsSave": "Salvar", + "ControllerSettingsClose": "Fechar", + "UserProfilesSelectedUserProfile": "Perfil de usuário selecionado:", + "UserProfilesSaveProfileName": "Salvar nome de perfil", + "UserProfilesChangeProfileImage": "Mudar imagem de perfil", + "UserProfilesAvailableUserProfiles": "Perfis de usuário disponíveis:", + "UserProfilesAddNewProfile": "Adicionar novo perfil", + "UserProfilesDelete": "Apagar", + "UserProfilesClose": "Fechar", + "ProfileImageSelectionTitle": "Seleção da imagem de perfil", + "ProfileImageSelectionHeader": "Escolha uma imagem de perfil", + "ProfileImageSelectionNote": "Você pode importar uma imagem customizada, ou selecionar um avatar do firmware", + "ProfileImageSelectionImportImage": "Importar arquivo de imagem", + "ProfileImageSelectionSelectAvatar": "Selecionar avatar do firmware", + "InputDialogTitle": "Diálogo de texto", + "InputDialogOk": "OK", + "InputDialogCancel": "Cancelar", + "InputDialogAddNewProfileTitle": "Escolha o nome de perfil", + "InputDialogAddNewProfileHeader": "Escreva o nome do perfil", + "InputDialogAddNewProfileSubtext": "(Máximo de caracteres: {0})", + "AvatarChoose": "Escolher", + "AvatarSetBackgroundColor": "Definir cor de fundo", + "AvatarClose": "Fechar", + "ControllerSettingsLoadProfileToolTip": "Carregar perfil", + "ControllerSettingsAddProfileToolTip": "Adicionar perfil", + "ControllerSettingsRemoveProfileToolTip": "Remover perfil", + "ControllerSettingsSaveProfileToolTip": "Salvar perfil", + "MenuBarFileToolsTakeScreenshot": "Salvar captura de tela", + "MenuBarFileToolsHideUi": "Hide UI", + "GameListContextMenuToggleFavorite": "Alternar favorito", + "GameListContextMenuToggleFavoriteToolTip": "Marca ou desmarca jogo como favorito", + "SettingsTabGeneralTheme": "Tema", + "SettingsTabGeneralThemeCustomTheme": "Diretório de tema customizado", + "SettingsTabGeneralThemeBaseStyle": "Estilo base", + "SettingsTabGeneralThemeBaseStyleDark": "Escuro", + "SettingsTabGeneralThemeBaseStyleLight": "Claro", + "SettingsTabGeneralThemeEnableCustomTheme": "Habilitar tema customizado", + "ButtonBrowse": "Procurar", + "ControllerSettingsConfigureGeneral": "Configurar", + "ControllerSettingsRumble": "Vibração", + "ControllerSettingsRumbleStrongMultiplier": "Multiplicador de vibração forte", + "ControllerSettingsRumbleWeakMultiplier": "Multiplicador de vibração fraca", + "DialogMessageSaveNotAvailableMessage": "Não há jogos salvos para {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Gostaria de criar o diretório de salvamento para esse jogo?", + "DialogConfirmationTitle": "Ryujinx - Confirmação", + "DialogUpdaterTitle": "Ryujinx - Atualizador", + "DialogErrorTitle": "Ryujinx - Erro", + "DialogWarningTitle": "Ryujinx - Alerta", + "DialogExitTitle": "Ryujinx - Sair", + "DialogErrorMessage": "Ryujinx encontrou um erro", + "DialogExitMessage": "Tem certeza que deseja fechar o Ryujinx?", + "DialogExitSubMessage": "Todos os dados que não foram salvos serão perdidos!", + "DialogMessageCreateSaveErrorMessage": "Ocorreu um erro ao criar o diretório de salvamento: {0}", + "DialogMessageFindSaveErrorMessage": "Ocorreu um erro ao tentar encontrar o diretório de salvamento: {0}", + "FolderDialogExtractTitle": "Escolha o diretório onde os arquivos serão extraídos", + "DialogNcaExtractionMessage": "Extraindo seção {0} de {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Extrator de seções NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Falha na extração. O NCA principal não foi encontrado no arquivo selecionado.", + "DialogNcaExtractionCheckLogErrorMessage": "Falha na extração. Leia o arquivo de log para mais informações.", + "DialogNcaExtractionSuccessMessage": "Extração concluída com êxito.", + "DialogUpdaterConvertFailedMessage": "Falha ao converter a versão atual do Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Cancelando atualização!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Você já está usando a versão mais recente do Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Ocorreu um erro ao tentar obter as informações de atualização do GitHub Release. Isso pode ser causado se uma nova versão estiver sendo compilado pelas Ações do GitHub. Tente novamente em alguns minutos.", + "DialogUpdaterConvertFailedGithubMessage": "Falha ao converter a versão do Ryujinx recebida do AppVeyor.", + "DialogUpdaterDownloadingMessage": "Baixando atualização...", + "DialogUpdaterExtractionMessage": "Extraindo atualização...", + "DialogUpdaterRenamingMessage": "Renomeando atualização...", + "DialogUpdaterAddingFilesMessage": "Adicionando nova atualização...", + "DialogUpdaterCompleteMessage": "Atualização concluída!", + "DialogUpdaterRestartMessage": "Deseja reiniciar o Ryujinx agora?", + "DialogUpdaterArchNotSupportedMessage": "Você não está rodando uma arquitetura de sistema suportada!", + "DialogUpdaterArchNotSupportedSubMessage": "(Apenas sistemas x64 são suportados!)", + "DialogUpdaterNoInternetMessage": "Você não está conectado à Internet!", + "DialogUpdaterNoInternetSubMessage": "Por favor, certifique-se de que você tem uma conexão funcional à Internet!", + "DialogUpdaterDirtyBuildMessage": "Você não pode atualizar uma compilação Dirty do Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Por favor, baixe o Ryujinx em https://ryujinx.org/ se está procurando por uma versão suportada.", + "DialogRestartRequiredMessage": "Reinicialização necessária", + "DialogThemeRestartMessage": "O tema foi salvo. Uma reinicialização é necessária para aplicar o tema.", + "DialogThemeRestartSubMessage": "Deseja reiniciar?", + "DialogFirmwareInstallEmbeddedMessage": "Gostaria de instalar o firmware incluso neste jogo? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Nenhum firmware instalado foi encontrado, mas Ryujinx conseguiu instalar o firmware {0} do jogo fornecido.\nO emulador será reiniciado.", + "DialogFirmwareNoFirmwareInstalledMessage": "Firmware não foi instalado", + "DialogFirmwareInstalledMessage": "Firmware {0} foi instalado", + "DialogOpenSettingsWindowLabel": "Abrir janela de configurações", + "DialogControllerAppletTitle": "Applet de controle", + "DialogMessageDialogErrorExceptionMessage": "Erro ao exibir diálogo de mensagem: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Erro ao exibir teclado virtual: {0}", + "DialogErrorAppletErrorExceptionMessage": "Erro ao exibir applet ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nPara mais informações sobre como corrigir esse erro, siga nosso Guia de Configuração.", + "DialogUserErrorDialogTitle": "Erro do Ryujinx ({0})", + "DialogAmiiboApiTitle": "API Amiibo", + "DialogAmiiboApiFailFetchMessage": "Um erro ocorreu ao tentar obter informações da API.", + "DialogAmiiboApiConnectErrorMessage": "Não foi possível conectar ao servidor da API Amiibo. O serviço pode estar fora do ar ou você precisa verificar sua conexão com a Internet.", + "DialogProfileInvalidProfileErrorMessage": "Perfil {0} é incompatível com o sistema de configuração de controle atual.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "O perfil Padrão não pode ser substituído", + "DialogProfileDeleteProfileTitle": "Apagando perfil", + "DialogProfileDeleteProfileMessage": "Essa ação é irreversível, tem certeza que deseja continuar?", + "DialogWarning": "Alerta", + "DialogPPTCDeletionMessage": "Você está prestes a apagar o cache PPTC para :\n\n{0}\n\nTem certeza que deseja continuar?", + "DialogPPTCDeletionErrorMessage": "Erro apagando cache PPTC em {0}: {1}", + "DialogShaderDeletionMessage": "Você está prestes a apagar o cache de Shader para :\n\n{0}\n\nTem certeza que deseja continuar?", + "DialogShaderDeletionErrorMessage": "Erro apagando o cache de Shader em {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx encontrou um erro", + "DialogInvalidTitleIdErrorMessage": "Erro de interface: O jogo selecionado não tem um ID de título válido", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Um firmware de sistema válido não foi encontrado em {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Instalar firmware {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "A versão do sistema {0} será instalada.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nIsso substituirá a versão do sistema atual {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nDeseja continuar?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Instalando firmware...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Versão do sistema {0} instalada com sucesso.", + "DialogUserProfileDeletionWarningMessage": "Não haveria nenhum perfil selecionado se o perfil atual fosse deletado", + "DialogUserProfileDeletionConfirmMessage": "Deseja deletar o perfil selecionado", + "DialogControllerSettingsModifiedConfirmMessage": "As configurações de controle atuais foram atualizadas.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Deseja salvar?", + "DialogLoadNcaErrorMessage": "{0}. Arquivo com erro: {1}", + "DialogDlcNoDlcErrorMessage": "O arquivo especificado não contém DLCs para o título selecionado!", + "DialogPerformanceCheckLoggingEnabledMessage": "Os logs de depuração estão ativos, esse recurso é feito para ser usado apenas por desenvolvedores.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Para melhor performance, é recomendável desabilitar os logs de depuração. Gostaria de desabilitar os logs de depuração agora?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "O despejo de shaders está ativo, esse recurso é feito para ser usado apenas por desenvolvedores.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Para melhor performance, é recomendável desabilitar o despejo de shaders. Gostaria de desabilitar o despejo de shaders agora?", + "DialogLoadAppGameAlreadyLoadedMessage": "Um jogo já foi carregado", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Por favor, pare a emulação ou feche o emulador antes de abrir outro jogo.", + "DialogUpdateAddUpdateErrorMessage": "O arquivo especificado não contém atualizações para o título selecionado!", + "DialogSettingsBackendThreadingWarningTitle": "Alerta - Threading da API gráfica", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx precisa ser reiniciado após mudar essa opção para que ela tenha efeito. Dependendo da sua plataforma, pode ser preciso desabilitar o multithreading do driver de vídeo quando usar o Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Recursos", + "SettingsTabGraphicsBackendMultithreading": "Multithreading da API gráfica:", + "CommonAuto": "Automático", + "CommonOff": "Desligado", + "CommonOn": "Ligado", + "InputDialogYes": "Sim", + "InputDialogNo": "Não", + "DialogProfileInvalidProfileNameErrorMessage": "O nome do arquivo contém caracteres inválidos. Por favor, tente novamente.", + "MenuBarOptionsPauseEmulation": "Pausar", + "MenuBarOptionsResumeEmulation": "Resumir", + "AboutUrlTooltipMessage": "Clique para abrir o site do Ryujinx no seu navegador padrão.", + "AboutDisclaimerMessage": "Ryujinx não é afiliado com a Nintendo™,\nou qualquer um de seus parceiros, de nenhum modo.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) é usado\nem nossa emulação de Amiibo.", + "AboutPatreonUrlTooltipMessage": "Clique para abrir a página do Patreon do Ryujinx no seu navegador padrão.", + "AboutGithubUrlTooltipMessage": "Clique para abrir a página do GitHub do Ryujinx no seu navegador padrão.", + "AboutDiscordUrlTooltipMessage": "Clique para abrir um convite ao servidor do Discord do Ryujinx no seu navegador padrão.", + "AboutTwitterUrlTooltipMessage": "Clique para abrir a página do Twitter do Ryujinx no seu navegador padrão.", + "AboutRyujinxAboutTitle": "Sobre:", + "AboutRyujinxAboutContent": "Ryujinx é um emulador de Nintendo Switch™.\nPor favor, nos dê apoio no Patreon.\nFique por dentro de todas as novidades no Twitter ou Discord.\nDesenvolvedores com interesse em contribuir podem conseguir mais informações no GitHub ou Discord.", + "AboutRyujinxMaintainersTitle": "Mantido por:", + "AboutRyujinxMaintainersContentTooltipMessage": "Clique para abrir a página de contribuidores no seu navegador padrão.", + "AboutRyujinxSupprtersTitle": "Apoiado no Patreon por:", + "AmiiboSeriesLabel": "Franquia Amiibo", + "AmiiboCharacterLabel": "Personagem", + "AmiiboScanButtonLabel": "Escanear", + "AmiiboOptionsShowAllLabel": "Exibir todos os Amiibos", + "AmiiboOptionsUsRandomTagLabel": "Hack: Usar Uuid de tag aleatório", + "DlcManagerTableHeadingEnabledLabel": "Habilitado", + "DlcManagerTableHeadingTitleIdLabel": "ID do título", + "DlcManagerTableHeadingContainerPathLabel": "Caminho do container", + "DlcManagerTableHeadingFullPathLabel": "Caminho completo", + "DlcManagerRemoveAllButton": "Remover todos", + "DlcManagerEnableAllButton": "Habilitar todos", + "DlcManagerDisableAllButton": "Desabilitar todos", + "MenuBarOptionsChangeLanguage": "Mudar idioma", + "CommonSort": "Ordenar", + "CommonShowNames": "Exibir nomes", + "CommonFavorite": "Favorito", + "OrderAscending": "Ascendente", + "OrderDescending": "Descendente", + "SettingsTabGraphicsFeatures": "Recursos & Melhorias", + "ErrorWindowTitle": "Janela de erro", + "ToggleDiscordTooltip": "Habilita ou desabilita Discord Rich Presence", + "AddGameDirBoxTooltip": "Escreva um diretório de jogo para adicionar à lista", + "AddGameDirTooltip": "Adicionar um diretório de jogo à lista", + "RemoveGameDirTooltip": "Remover diretório de jogo selecionado", + "CustomThemeCheckTooltip": "Habilita ou desabilita temas customizados na interface gráfica", + "CustomThemePathTooltip": "Diretório do tema customizado", + "CustomThemeBrowseTooltip": "Navegar até um tema customizado", + "DockModeToggleTooltip": "Habilita ou desabilita modo TV", + "DirectKeyboardTooltip": "Habilita ou desabilita \"acesso direto ao teclado (HID)\" (Permite que o jogo acesse o seu teclado como dispositivo de entrada de texto)", + "DirectMouseTooltip": "Habilita ou desabilita \"acesso direto ao mouse (HID)\" (Permite que o jogo acesse o seu mouse como dispositivo apontador)", + "RegionTooltip": "Mudar a região do sistema", + "LanguageTooltip": "Mudar o idioma do sistema", + "TimezoneTooltip": "Mudar o fuso-horário do sistema", + "TimeTooltip": "Mudar a hora do sistema", + "VSyncToggleTooltip": "Habilita ou desabilita a sincronia vertical", + "PptcToggleTooltip": "Habilita ou desabilita PPTC", + "FsIntegrityToggleTooltip": "Habilita ou desabilita verificação de integridade dos arquivos do jogo", + "AudioBackendTooltip": "Mudar biblioteca de áudio", + "MemoryManagerTooltip": "Muda como a memória do sistema convidado é acessada. Tem um grande impacto na performance da CPU emulada.", + "MemoryManagerSoftwareTooltip": "Usar uma tabela de página via software para tradução de endereços. Maior precisão, porém performance mais baixa.", + "MemoryManagerHostTooltip": "Mapeia memória no espaço de endereço hóspede diretamente. Compilação e execução do JIT muito mais rápida.", + "MemoryManagerUnsafeTooltip": "Mapeia memória diretamente, mas sem limitar o acesso ao espaço de endereçamento do sistema convidado. Mais rápido, porém menos seguro. O aplicativo convidado pode acessar memória de qualquer parte do Ryujinx, então apenas rode programas em que você confia nesse modo.", + "DRamTooltip": "Expande a memória do sistema emulado de 4GiB para 6GiB", + "IgnoreMissingServicesTooltip": "Habilita ou desabilita a opção de ignorar serviços não implementados", + "GraphicsBackendThreadingTooltip": "Habilita multithreading do backend gráfico", + "GalThreadingTooltip": "Executa comandos do backend gráfico em uma segunda thread. Permite multithreading em tempo de execução da compilação de shader, diminui os travamentos, e melhora performance em drivers sem suporte embutido a multithreading. Pequena variação na performance máxima em drivers com suporte a multithreading. Ryujinx pode precisar ser reiniciado para desabilitar adequadamente o multithreading embutido do driver, ou você pode precisar fazer isso manualmente para ter a melhor performance.", + "ShaderCacheToggleTooltip": "Habilita ou desabilita o cache de shader", + "ResolutionScaleTooltip": "Escala de resolução aplicada às texturas de renderização", + "ResolutionScaleEntryTooltip": "Escala de resolução de ponto flutuante, como 1.5. Valores não inteiros tem probabilidade maior de causar problemas ou quebras.", + "AnisotropyTooltip": "Nível de filtragem anisotrópica (deixe em Auto para usar o valor solicitado pelo jogo)", + "AspectRatioTooltip": "Taxa de proporção aplicada à janela do renderizador.", + "ShaderDumpPathTooltip": "Diretòrio de despejo de shaders", + "FileLogTooltip": "Habilita ou desabilita log para um arquivo no disco", + "StubLogTooltip": "Habilita ou desabilita exibição de mensagens de stub", + "InfoLogTooltip": "Habilita ou desabilita exibição de mensagens informativas", + "WarnLogTooltip": "Habilita ou desabilita exibição de mensagens de alerta", + "ErrorLogTooltip": "Habilita ou desabilita exibição de mensagens de erro", + "TraceLogTooltip": "Habilita ou desabilita exibição de mensagens de rastreamento", + "GuestLogTooltip": "Habilita ou desabilita exibição de mensagens do programa convidado", + "FileAccessLogTooltip": "Habilita ou desabilita exibição de mensagens do acesso de arquivos", + "FSAccessLogModeTooltip": "Habilita exibição de mensagens de acesso ao sistema de arquivos no console. Modos permitidos são 0-3", + "DeveloperOptionTooltip": "Use com cuidado", + "OpenGlLogLevel": "Requer que os níveis de log apropriados estejaam habilitados", + "DebugLogTooltip": "Habilita exibição de mensagens de depuração", + "LoadApplicationFileTooltip": "Abre o navegador de arquivos para seleção de um arquivo do Switch compatível a ser carregado", + "LoadApplicationFolderTooltip": "Abre o navegador de pastas para seleção de pasta extraída do Switch compatível a ser carregada", + "OpenRyujinxFolderTooltip": "Abre o diretório do sistema de arquivos do Ryujinx", + "OpenRyujinxLogsTooltip": "Abre o diretório onde os logs são salvos", + "ExitTooltip": "Sair do Ryujinx", + "OpenSettingsTooltip": "Abrir janela de configurações", + "OpenProfileManagerTooltip": "Abrir janela de gerenciamento de perfis", + "StopEmulationTooltip": "Parar emulação do jogo atual e voltar a seleção de jogos", + "CheckUpdatesTooltip": "Verificar por atualizações para o Ryujinx", + "OpenAboutTooltip": "Abrir janela sobre", + "GridSize": "Tamanho da grade", + "GridSizeTooltip": "Mudar tamanho dos items da grade", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Português do Brasil", + "AboutRyujinxContributorsButtonHeader": "Ver todos os contribuidores", + "SettingsTabSystemAudioVolume": "Volume:", + "AudioVolumeTooltip": "Mudar volume do áudio", + "SettingsTabSystemEnableInternetAccess": "Habilitar acesso à internet do programa convidado", + "EnableInternetAccessTooltip": "Habilita acesso à internet do programa convidado. Se habilitado, o aplicativo vai se comportar como se o sistema Switch emulado estivesse conectado a Internet. Note que em alguns casos, aplicativos podem acessar a Internet mesmo com essa opção desabilitada", + "GameListContextMenuManageCheatToolTip": "Gerenciar Cheats", + "GameListContextMenuManageCheat": "Gerenciar Cheats", + "ControllerSettingsStickRange": "Intervalo:", + "DialogStopEmulationTitle": "Ryujinx - Parar emulação", + "DialogStopEmulationMessage": "Tem certeza que deseja parar a emulação?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "Áudio", + "SettingsTabNetwork": "Rede", + "SettingsTabNetworkConnection": "Conexão de rede", + "SettingsTabCpuCache": "Cache da CPU", + "SettingsTabCpuMemory": "Memória da CPU", + "DialogUpdaterFlatpakNotSupportedMessage": "Por favor, atualize o Ryujinx pelo FlatHub.", + "UpdaterDisabledWarningTitle": "Atualizador desabilitado!", + "GameListContextMenuOpenSdModsDirectory": "Abrir diretório de mods Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Abre o diretório alternativo Atmosphere no cartão SD que contém mods para o aplicativo", + "ControllerSettingsRotate90": "Rodar 90° sentido horário", + "IconSize": "Tamanho do ícone", + "IconSizeTooltip": "Muda o tamanho do ícone do jogo", + "MenuBarOptionsShowConsole": "Exibir console", + "ShaderCachePurgeError": "Erro ao deletar o shader em {0}: {1}", + "UserErrorNoKeys": "Chaves não encontradas", + "UserErrorNoFirmware": "Firmware não encontrado", + "UserErrorFirmwareParsingFailed": "Erro na leitura do Firmware", + "UserErrorApplicationNotFound": "Aplicativo não encontrado", + "UserErrorUnknown": "Erro desconhecido", + "UserErrorUndefined": "Erro indefinido", + "UserErrorNoKeysDescription": "Ryujinx não conseguiu encontrar o seu arquivo 'prod.keys'", + "UserErrorNoFirmwareDescription": "Ryujinx não conseguiu encontrar nenhum Firmware instalado", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx não conseguiu ler o Firmware fornecido. Geralmente isso é causado por chaves desatualizadas.", + "UserErrorApplicationNotFoundDescription": "Ryujinx não conseguiu encontrar um aplicativo válido no caminho fornecido.", + "UserErrorUnknownDescription": "Um erro desconhecido foi encontrado!", + "UserErrorUndefinedDescription": "Um erro indefinido occoreu! Isso não deveria acontecer, por favor contate um desenvolvedor!", + "OpenSetupGuideMessage": "Abrir o guia de configuração", + "NoUpdate": "Sem atualizações", + "TitleUpdateVersionLabel": "Versão {0} - {1}", + "RyujinxInfo": "Ryujinx - Informação", + "RyujinxConfirm": "Ryujinx - Confirmação", + "FileDialogAllTypes": "Todos os tipos", + "Never": "Nunca", + "SwkbdMinCharacters": "Deve ter pelo menos {0} caracteres", + "SwkbdMinRangeCharacters": "Deve ter entre {0}-{1} caracteres", + "SoftwareKeyboard": "Teclado por Software", + "DialogControllerAppletMessagePlayerRange": "O aplicativo requer {0} jogador(es) com:\n\nTIPOS: {1}\n\nJOGADORES: {2}\n\n{3}Por favor, abra as configurações e reconfigure os controles agora ou clique em Fechar.", + "DialogControllerAppletMessage": "O aplicativo requer exatamente {0} jogador(es) com:\n\nTIPOS: {1}\n\nJOGADORES: {2}\n\n{3}Por favor, abra as configurações e reconfigure os controles agora ou clique em Fechar.", + "DialogControllerAppletDockModeSet": "Modo TV ativado. Portátil também não é válido.\n\n", + "UpdaterRenaming": "Renomeando arquivos antigos...", + "UpdaterRenameFailed": "O atualizador não conseguiu renomear o arquivo: {0}", + "UpdaterAddingFiles": "Adicionando novos arquivos...", + "UpdaterExtracting": "Extraíndo atualização...", + "UpdaterDownloading": "Baixando atualização...", + "Game": "Jogo", + "Docked": "TV", + "Handheld": "Portátil", + "ConnectionError": "Erro de conexão.", + "AboutPageDeveloperListMore": "{0} e mais...", + "ApiError": "Erro de API.", + "LoadingHeading": "Carregando {0}", + "CompilingPPTC": "Compilando PTC", + "CompilingShaders": "Compilando Shaders", + "AllKeyboards": "Todos os teclados", + "OpenFileDialogTitle": "Selecione um arquivo suportado para abrir", + "OpenFolderDialogTitle": "Selecione um diretório com um jogo extraído", + "AllSupportedFormats": "Todos os formatos suportados", + "RyujinxUpdater": "Atualizador do Ryujinx", + "SettingsTabHotkeys": "Atalhos do teclado", + "SettingsTabHotkeysHotkeys": "Atalhos do teclado", + "SettingsTabHotkeysToggleVsyncHotkey": "Mudar VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Captura de tela:", + "SettingsTabHotkeysShowUiHotkey": "Exibir UI:", + "SettingsTabHotkeysPauseHotkey": "Pausar:", + "SettingsTabHotkeysToggleMuteHotkey": "Mudo:", + "ControllerMotionTitle": "Configurações do controle de movimento", + "ControllerRumbleTitle": "Configurações de vibração", + "SettingsSelectThemeFileDialogTitle": "Selecionar arquivo do tema", + "SettingsXamlThemeFile": "Arquivo de tema Xaml", + "AvatarWindowTitle": "Gerenciar contas - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Desconhecido", + "Usage": "Uso", + "Writable": "Gravável", + "SelectDlcDialogTitle": "Selecionar arquivos de DLC", + "SelectUpdateDialogTitle": "Selecionar arquivos de atualização", + "UserProfileWindowTitle": "Gerenciador de perfis de usuário", + "CheatWindowTitle": "Gerenciador de Cheats", + "DlcWindowTitle": "Gerenciador de DLC", + "UpdateWindowTitle": "Gerenciador de atualizações", + "CheatWindowHeading": "Cheats disponíveis para {0} [{1}]", + "DlcWindowHeading": "{0} DLCs disponíveis para {1} ({2})", + "UserProfilesEditProfile": "Editar selecionado", + "Cancel": "Cancelar", + "Save": "Salvar", + "Discard": "Descartar", + "UserProfilesSetProfileImage": "Definir imagem de perfil", + "UserProfileEmptyNameError": "É necessário um nome", + "UserProfileNoImageError": "A imagem de perfil deve ser definida", + "GameUpdateWindowHeading": "{0} atualizações disponíveis para {1} ({2})", + "SettingsTabHotkeysResScaleUpHotkey": "Aumentar a resolução:", + "SettingsTabHotkeysResScaleDownHotkey": "Diminuir a resolução:", + "UserProfilesName": "Nome:", + "UserProfilesUserId": "ID de usuário:", + "SettingsTabGraphicsBackend": "Backend gráfico", + "SettingsTabGraphicsBackendTooltip": "Backend gráfico a ser usado", + "SettingsEnableTextureRecompression": "Habilitar recompressão de texturas", + "SettingsEnableTextureRecompressionTooltip": "Comprime certas texturas para reduzir o uso da VRAM.\n\nRecomendado para uso com GPUs com menos de 4GB VRAM.\n\nEm caso de dúvida, deixe DESLIGADO.", + "SettingsTabGraphicsPreferredGpu": "GPU preferencial", + "SettingsTabGraphicsPreferredGpuTooltip": "Selecione a placa de vídeo que será usada com o backend gráfico Vulkan.\n\nNão afeta a GPU que OpenGL usará.\n\nSelecione \"dGPU\" em caso de dúvida. Se não houver nenhuma, não mexa.", + "SettingsAppRequiredRestartMessage": "Reinicialização do Ryujinx necessária", + "SettingsGpuBackendRestartMessage": "Configurações do backend gráfico ou da GPU foram alteradas. Uma reinicialização é necessária para que as mudanças tenham efeito.", + "SettingsGpuBackendRestartSubMessage": "Deseja reiniciar agora?", + "RyujinxUpdaterMessage": "Você quer atualizar o Ryujinx para a última versão?", + "SettingsTabHotkeysVolumeUpHotkey": "Aumentar volume:", + "SettingsTabHotkeysVolumeDownHotkey": "Diminuir volume:", + "SettingsEnableMacroHLE": "Habilitar emulação de alto nível para Macros", + "SettingsEnableMacroHLETooltip": "Habilita emulação de alto nível de códigos Macro da GPU.\n\nMelhora a performance, mas pode causar problemas gráficos em alguns jogos.\n\nEm caso de dúvida, deixe ATIVADO.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Gerenciar jogos salvos", + "DeleteUserSave": "Deseja apagar o jogo salvo do usuário para este jogo?", + "IrreversibleActionNote": "Esta ação não é reversível.", + "SaveManagerHeading": "Gerenciar jogos salvos para {0}", + "SaveManagerTitle": "Gerenciador de jogos salvos", + "Name": "Nome", + "Size": "Tamanho", + "Search": "Buscar", + "UserProfilesRecoverLostAccounts": "Recuperar contas perdidas", + "Recover": "Recuperar", + "UserProfilesRecoverHeading": "Jogos salvos foram encontrados para as seguintes contas" +} diff --git a/src/Ryujinx.Ava/Assets/Locales/ru_RU.json b/src/Ryujinx.Ava/Assets/Locales/ru_RU.json new file mode 100644 index 00000000..ea60bd91 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/ru_RU.json @@ -0,0 +1,614 @@ +{ + "Language": "Русский", + "MenuBarFileOpenApplet": "Открыть апплет", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Открыть апплет Mii Editor в автономном режиме.", + "SettingsTabInputDirectMouseAccess": "Прямой доступ с помощью мыши", + "SettingsTabSystemMemoryManagerMode": "Режим диспетчера памяти:", + "SettingsTabSystemMemoryManagerModeSoftware": "Программное обеспечение", + "SettingsTabSystemMemoryManagerModeHost": "Хост (быстро)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Хост не установлен (самый быстрый, небезопасный)", + "MenuBarFile": "_Файл", + "MenuBarFileOpenFromFile": "_Загрузить приложение из файла", + "MenuBarFileOpenUnpacked": "Загрузить _Распакованную игру", + "MenuBarFileOpenEmuFolder": "Открыть папку Ryujinx", + "MenuBarFileOpenLogsFolder": "Открыть папку журналов", + "MenuBarFileExit": "_Выход", + "MenuBarOptions": "Опции", + "MenuBarOptionsToggleFullscreen": "Включить полноэкранный режим", + "MenuBarOptionsStartGamesInFullscreen": "Запустить игру в полноэкранном режиме", + "MenuBarOptionsStopEmulation": "Остановить эмуляцию", + "MenuBarOptionsSettings": "_Параметры", + "MenuBarOptionsManageUserProfiles": "_Управление профилями пользователей", + "MenuBarActions": "_Действия", + "MenuBarOptionsSimulateWakeUpMessage": "Имитировать сообщение пробуждения", + "MenuBarActionsScanAmiibo": "Сканировать Amiibo", + "MenuBarTools": "_Инструменты", + "MenuBarToolsInstallFirmware": "Установить прошивку", + "MenuBarFileToolsInstallFirmwareFromFile": "Установить прошивку из XCI или ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Установить прошивку из каталога", + "MenuBarHelp": "Помощь", + "MenuBarHelpCheckForUpdates": "Проверить обновление", + "MenuBarHelpAbout": "О", + "MenuSearch": "Поиск...", + "GameListHeaderFavorite": "Избранные", + "GameListHeaderIcon": "Значок", + "GameListHeaderApplication": "Название", + "GameListHeaderDeveloper": "Разработчик", + "GameListHeaderVersion": "Версия", + "GameListHeaderTimePlayed": "Время воспроизведения", + "GameListHeaderLastPlayed": "Последняя игра", + "GameListHeaderFileExtension": "Расширение файла", + "GameListHeaderFileSize": "Размер файла", + "GameListHeaderPath": "Путь", + "GameListContextMenuOpenUserSaveDirectory": "Открыть каталог сохранений пользователя", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Открывает каталог, содержащий пользовательское сохранение приложения", + "GameListContextMenuOpenDeviceSaveDirectory": "Открыть каталог пользовательских устройств", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Открывает каталог, содержащий сохранение устройства приложения", + "GameListContextMenuOpenBcatSaveDirectory": "Открыть каталог пользователей BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Открывает каталог, содержащий BCAT сохранения приложения.", + "GameListContextMenuManageTitleUpdates": "Управление обновлениями заголовков", + "GameListContextMenuManageTitleUpdatesToolTip": "Открывает окно управления обновлением заголовков", + "GameListContextMenuManageDlc": "Управление DLC", + "GameListContextMenuManageDlcToolTip": "Открывает окно управления DLC", + "GameListContextMenuOpenModsDirectory": "Открыть каталог модификаций", + "GameListContextMenuOpenModsDirectoryToolTip": "Открывает каталог, содержащий модификации приложения", + "GameListContextMenuCacheManagement": "Управление кэшем", + "GameListContextMenuCacheManagementPurgePptc": "Очистить кэш PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Удаляет кэш PPTC приложения", + "GameListContextMenuCacheManagementPurgeShaderCache": "Очистить кэш шейдеров", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Удаляет кеш шейдеров приложения", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Открыть PPTC каталог", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Открывает каталог, содержащий PPTC кэш приложения", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Открыть каталог кэша шейдеров", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Открывает каталог, содержащий кэш шейдеров приложения", + "GameListContextMenuExtractData": "Извлечь данные", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Извлеrftn раздел ExeFS из текущей конфигурации приложения (включая обновления)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Извлекает раздел RomFS из текущей конфигурации приложения (включая обновления)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Извлекает раздел логотипа из текущей конфигурации приложения (включая обновления)", + "StatusBarGamesLoaded": "{0}/{1} Игр загружено", + "StatusBarSystemVersion": "Версия системы: {0}", + "Settings": "Параметры", + "SettingsTabGeneral": "Пользовательский интерфейс", + "SettingsTabGeneralGeneral": "Общее", + "SettingsTabGeneralEnableDiscordRichPresence": "Включить расширенное присутствие Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Проверять наличие обновлений при запуске", + "SettingsTabGeneralShowConfirmExitDialog": "Показать диалоговое окно \"Подтвердить выход\"", + "SettingsTabGeneralHideCursorOnIdle": "Скрыть курсор в режиме ожидания", + "SettingsTabGeneralGameDirectories": "Каталоги игр", + "SettingsTabGeneralAdd": "Добавить", + "SettingsTabGeneralRemove": "Удалить", + "SettingsTabSystem": "Система", + "SettingsTabSystemCore": "Основные настройки", + "SettingsTabSystemSystemRegion": "Регион Системы:", + "SettingsTabSystemSystemRegionJapan": "Япония", + "SettingsTabSystemSystemRegionUSA": "США", + "SettingsTabSystemSystemRegionEurope": "Европа", + "SettingsTabSystemSystemRegionAustralia": "Австралия", + "SettingsTabSystemSystemRegionChina": "Китай", + "SettingsTabSystemSystemRegionKorea": "Корея", + "SettingsTabSystemSystemRegionTaiwan": "Тайвань", + "SettingsTabSystemSystemLanguage": "Язык системы:", + "SettingsTabSystemSystemLanguageJapanese": "Японский", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Английский (США)", + "SettingsTabSystemSystemLanguageFrench": "Французский", + "SettingsTabSystemSystemLanguageGerman": "Германский", + "SettingsTabSystemSystemLanguageItalian": "Итальянский", + "SettingsTabSystemSystemLanguageSpanish": "Испанский", + "SettingsTabSystemSystemLanguageChinese": "Китайский", + "SettingsTabSystemSystemLanguageKorean": "Корейский", + "SettingsTabSystemSystemLanguageDutch": "Нидерландский", + "SettingsTabSystemSystemLanguagePortuguese": "Португальский", + "SettingsTabSystemSystemLanguageRussian": "Русский", + "SettingsTabSystemSystemLanguageTaiwanese": "Тайванский", + "SettingsTabSystemSystemLanguageBritishEnglish": "Английский (Британия)", + "SettingsTabSystemSystemLanguageCanadianFrench": "Французский (Канада)", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Испанский (Латиноамериканский)", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Китайский упрощённый", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Китайский традиционный", + "SettingsTabSystemSystemTimeZone": "Часовой пояс системы:", + "SettingsTabSystemSystemTime": "Время системы:", + "SettingsTabSystemEnableVsync": "Включить вертикальную синхронизацию", + "SettingsTabSystemEnablePptc": "Включить PPTC (Profiled Persistent Translation Cache)", + "SettingsTabSystemEnableFsIntegrityChecks": "Включить проверку целостности FS", + "SettingsTabSystemAudioBackend": "Аудио бэкэнд:", + "SettingsTabSystemAudioBackendDummy": "Муляж", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Хаки", + "SettingsTabSystemHacksNote": " (Эти многие настройки вызывают нестабильность)", + "SettingsTabSystemExpandDramSize": "Увеличение размера DRAM до 6GiB", + "SettingsTabSystemIgnoreMissingServices": "Игнорировать отсутствующие службы", + "SettingsTabGraphics": "Графика", + "SettingsTabGraphicsAPI": "Графические API", + "SettingsTabGraphicsEnableShaderCache": "Включить кэш шейдеров", + "SettingsTabGraphicsAnisotropicFiltering": "Анизотропная фильтрация:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Автоматически", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Масштаб:", + "SettingsTabGraphicsResolutionScaleCustom": "Пользовательский (не рекомендуется)", + "SettingsTabGraphicsResolutionScaleNative": "Родной (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Соотношение сторон:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Растянуть до размеров окна", + "SettingsTabGraphicsDeveloperOptions": "Параметры разработчика", + "SettingsTabGraphicsShaderDumpPath": "Путь дампа графического шейдера:", + "SettingsTabLogging": "Журналирование", + "SettingsTabLoggingLogging": "Журналирование", + "SettingsTabLoggingEnableLoggingToFile": "Включить запись в файл", + "SettingsTabLoggingEnableStubLogs": "Включить журналы-заглушки", + "SettingsTabLoggingEnableInfoLogs": "Включить информационные журналы", + "SettingsTabLoggingEnableWarningLogs": "Включить журналы предупреждений", + "SettingsTabLoggingEnableErrorLogs": "Включить журналы ошибок", + "SettingsTabLoggingEnableTraceLogs": "Включить журнал трассировки", + "SettingsTabLoggingEnableGuestLogs": "Включить гостевые журналы", + "SettingsTabLoggingEnableFsAccessLogs": "Включить журналы доступа Fs", + "SettingsTabLoggingFsGlobalAccessLogMode": "Режим журнала глобального доступа Fs:", + "SettingsTabLoggingDeveloperOptions": "Параметры разработчика (ВНИМАНИЕ: снизит производительность)", + "SettingsTabLoggingGraphicsBackendLogLevel": "Graphics Backend Log Level:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Ничего", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Ошибка", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Замедления", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Всё", + "SettingsTabLoggingEnableDebugLogs": "Включить журналы отладки", + "SettingsTabInput": "Управление", + "SettingsTabInputEnableDockedMode": "Включить режим закрепления", + "SettingsTabInputDirectKeyboardAccess": "Прямой доступ с клавиатуры", + "SettingsButtonSave": "Сохранить", + "SettingsButtonClose": "Закрыть", + "SettingsButtonOk": "Ок", + "SettingsButtonCancel": "Отмена", + "SettingsButtonApply": "Применить", + "ControllerSettingsPlayer": "Игрок", + "ControllerSettingsPlayer1": "Игрок 1", + "ControllerSettingsPlayer2": "Игрок 2", + "ControllerSettingsPlayer3": "Игрок 3", + "ControllerSettingsPlayer4": "Игрок 4", + "ControllerSettingsPlayer5": "Игрок 5", + "ControllerSettingsPlayer6": "Игрок 6", + "ControllerSettingsPlayer7": "Игрок 7", + "ControllerSettingsPlayer8": "Игрок 8", + "ControllerSettingsHandheld": "Портативный", + "ControllerSettingsInputDevice": "Устройство ввода", + "ControllerSettingsRefresh": "Обновить", + "ControllerSettingsDeviceDisabled": "Отключить", + "ControllerSettingsControllerType": "Тип контроллера", + "ControllerSettingsControllerTypeHandheld": "Портативный", + "ControllerSettingsControllerTypeProController": "Pro Controller", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon Пара", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Левый", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon Правый", + "ControllerSettingsProfile": "Профиль", + "ControllerSettingsProfileDefault": "По умолчанию", + "ControllerSettingsLoad": "Загрузить", + "ControllerSettingsAdd": "Добавить", + "ControllerSettingsRemove": "Удалить", + "ControllerSettingsButtons": "Кнопки", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Направляющая панель", + "ControllerSettingsDPadUp": "Верх", + "ControllerSettingsDPadDown": "Низ", + "ControllerSettingsDPadLeft": "Лево", + "ControllerSettingsDPadRight": "Право", + "ControllerSettingsLStick": "Левый стик", + "ControllerSettingsLStickButton": "Кнопки", + "ControllerSettingsLStickUp": "Верх", + "ControllerSettingsLStickDown": "Низ", + "ControllerSettingsLStickLeft": "Лево", + "ControllerSettingsLStickRight": "Право", + "ControllerSettingsLStickStick": "Стик", + "ControllerSettingsLStickInvertXAxis": "Перевернуть стик X", + "ControllerSettingsLStickInvertYAxis": "Перевернуть стик Y", + "ControllerSettingsLStickDeadzone": "Мёртвая зона:", + "ControllerSettingsRStick": "Правый стик", + "ControllerSettingsRStickButton": "Кнопки", + "ControllerSettingsRStickUp": "Верх", + "ControllerSettingsRStickDown": "Низ", + "ControllerSettingsRStickLeft": "Лево", + "ControllerSettingsRStickRight": "Право", + "ControllerSettingsRStickStick": "Стик", + "ControllerSettingsRStickInvertXAxis": "Перевернуть стик X", + "ControllerSettingsRStickInvertYAxis": "Перевернуть стик Y", + "ControllerSettingsRStickDeadzone": "Мёртвая зона:", + "ControllerSettingsTriggersLeft": "Триггеры слева", + "ControllerSettingsTriggersRight": "Триггеры справа", + "ControllerSettingsTriggersButtonsLeft": "Триггерные кнопки слева", + "ControllerSettingsTriggersButtonsRight": "Триггерные кнопки справа", + "ControllerSettingsTriggers": "Триггеры", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Левый кнопки", + "ControllerSettingsExtraButtonsRight": "Правые кнопки", + "ControllerSettingsMisc": "Разное", + "ControllerSettingsTriggerThreshold": "Порог срабатывания:", + "ControllerSettingsMotion": "Движение", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Используйте движение, совместимое с CemuHook", + "ControllerSettingsMotionControllerSlot": "Слот контроллера:", + "ControllerSettingsMotionMirrorInput": "Зеркальный ввод", + "ControllerSettingsMotionRightJoyConSlot": "Правый JoyCon слот:", + "ControllerSettingsMotionServerHost": "Хост сервера:", + "ControllerSettingsMotionGyroSensitivity": "Чувствительность гироскопа:", + "ControllerSettingsMotionGyroDeadzone": "Мертвая зона гироскопа:", + "ControllerSettingsSave": "Сохранить", + "ControllerSettingsClose": "Закрыть", + "UserProfilesSelectedUserProfile": "Выбранный пользовательский профиль:", + "UserProfilesSaveProfileName": "Сохранить пользовательский профиль", + "UserProfilesChangeProfileImage": "Изменить изображение профиля", + "UserProfilesAvailableUserProfiles": "Доступные профили пользователей:", + "UserProfilesAddNewProfile": "Добавить новый профиль", + "UserProfilesDelete": "Удалить", + "UserProfilesClose": "Закрыть", + "ProfileImageSelectionTitle": "Выбор изображения профиля", + "ProfileImageSelectionHeader": "Выберите изображение профиля", + "ProfileImageSelectionNote": "Вы можете импортировать собственное изображение профиля или выбрать аватар из системной прошивки.", + "ProfileImageSelectionImportImage": "Импорт файла изображени", + "ProfileImageSelectionSelectAvatar": "Выберите аватар прошивки", + "InputDialogTitle": "Диалоговое окно ввода", + "InputDialogOk": "Да", + "InputDialogCancel": "Закрыть", + "InputDialogAddNewProfileTitle": "Выберите имя профиля", + "InputDialogAddNewProfileHeader": "Пожалуйста, введите имя профиля", + "InputDialogAddNewProfileSubtext": "(Максимальная длина: {0})", + "AvatarChoose": "Выбор", + "AvatarSetBackgroundColor": "Установить цвет фона", + "AvatarClose": "Закрыть", + "ControllerSettingsLoadProfileToolTip": "Загрузить профиль", + "ControllerSettingsAddProfileToolTip": "Добавить профил", + "ControllerSettingsRemoveProfileToolTip": "Удалить профиль", + "ControllerSettingsSaveProfileToolTip": "Сохранить профиль", + "MenuBarFileToolsTakeScreenshot": "Сделать снимок экрана", + "MenuBarFileToolsHideUi": "Скрыть UI", + "GameListContextMenuToggleFavorite": "Переключить Избранное", + "GameListContextMenuToggleFavoriteToolTip": "Переключить любимый статус игры", + "SettingsTabGeneralTheme": "Тема", + "SettingsTabGeneralThemeCustomTheme": "Пользовательский путь к теме", + "SettingsTabGeneralThemeBaseStyle": "Базовый стиль", + "SettingsTabGeneralThemeBaseStyleDark": "Тёмная", + "SettingsTabGeneralThemeBaseStyleLight": "Светлая", + "SettingsTabGeneralThemeEnableCustomTheme": "Включить пользовательскую тему", + "ButtonBrowse": "Обзор", + "ControllerSettingsConfigureGeneral": "Настройка", + "ControllerSettingsRumble": "Вибрация", + "ControllerSettingsRumbleStrongMultiplier": "Множитель сильной вибрации", + "ControllerSettingsRumbleWeakMultiplier": "Множитель слабой вибрации", + "DialogMessageSaveNotAvailableMessage": "Нет сохраненных данных для {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Хотите создать данные сохранения для этой игры?", + "DialogConfirmationTitle": "Ryujinx - Подтверждение", + "DialogUpdaterTitle": "Ryujinx - Обновление", + "DialogErrorTitle": "Ryujinx - Ошибка", + "DialogWarningTitle": "Ryujinx - Предупреждение", + "DialogExitTitle": "Ryujinx - Выход", + "DialogErrorMessage": "Ryujinx обнаружил ошибку", + "DialogExitMessage": "Вы уверены, что хотите закрыть Ryujinx?", + "DialogExitSubMessage": "Все несохраненные данные будут потеряны!", + "DialogMessageCreateSaveErrorMessage": "Произошла ошибка при создании указанных данных сохранения: {0}", + "DialogMessageFindSaveErrorMessage": "Произошла ошибка при поиске указанных данных сохранения: {0}", + "FolderDialogExtractTitle": "Выберите папку для извлечения", + "DialogNcaExtractionMessage": "Извлечение {0} раздел от {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Экстрактор разделов NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Ошибка извлечения. Основной NCA не присутствовал в выбранном файле.", + "DialogNcaExtractionCheckLogErrorMessage": "Ошибка извлечения. Прочтите файл журнала для получения дополнительной информации.", + "DialogNcaExtractionSuccessMessage": "Извлечение завершено успешно.", + "DialogUpdaterConvertFailedMessage": "Не удалось преобразовать текущую версию Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Отмена обновления!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Вы уже используете самую последнюю версию Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Произошла ошибка при попытке получить информацию о выпуске от GitHub Release. Это может быть вызвано, если GitHub Actions компилирует новый релиз. Попробуйте ещё раз через несколько минут.", + "DialogUpdaterConvertFailedGithubMessage": "Не удалось преобразовать полученную версию Ryujinx из Github Release.", + "DialogUpdaterDownloadingMessage": "Загрузка обновления...", + "DialogUpdaterExtractionMessage": "Извлечение обновления...", + "DialogUpdaterRenamingMessage": "Переименование обновления...", + "DialogUpdaterAddingFilesMessage": "Добавление нового обновления...", + "DialogUpdaterCompleteMessage": "Обновление завершено!", + "DialogUpdaterRestartMessage": "Вы хотите перезапустить Ryujinx сейчас?", + "DialogUpdaterArchNotSupportedMessage": "Вы используете не поддерживаемую системную архитектуру!", + "DialogUpdaterArchNotSupportedSubMessage": "(Поддерживаются только системы x64!)", + "DialogUpdaterNoInternetMessage": "Вы не подключены к Интернету!", + "DialogUpdaterNoInternetSubMessage": "Убедитесь, что у вас есть работающее подключение к Интернету!", + "DialogUpdaterDirtyBuildMessage": "Вы не можете обновить Dirty Build Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Загрузите Ryujinx по адресу https://ryujinx.org/ , если вам нужна поддерживаемая версия.", + "DialogRestartRequiredMessage": "Требуется перезагрузка", + "DialogThemeRestartMessage": "Тема сохранена. Для применения темы требуется перезагрузка.", + "DialogThemeRestartSubMessage": "Вы хотите перезапустить?", + "DialogFirmwareInstallEmbeddedMessage": "Хотите установить прошивку, встроенную в эту игру? (Прошивка {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Установленная прошивка не найдена, но Ryujinx удалось установить прошивку {0} из предоставленной игры.\nЭмулятор запустится.", + "DialogFirmwareNoFirmwareInstalledMessage": "Прошивка не установлена", + "DialogFirmwareInstalledMessage": "Прошивка {0} была установлена", + "DialogOpenSettingsWindowLabel": "Открыть окно настроек", + "DialogControllerAppletTitle": "Апплет контроллера", + "DialogMessageDialogErrorExceptionMessage": "Ошибка отображения диалогового окна сообщений: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Ошибка отображения программной клавиатуры: {0}", + "DialogErrorAppletErrorExceptionMessage": "Ошибка отображения диалогового окна ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nДля получения дополнительной информации о том, как исправить эту ошибку, следуйте нашему Руководству по установке.", + "DialogUserErrorDialogTitle": "Ошибка Ryujinx! ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Произошла ошибка при получении информации из API.", + "DialogAmiiboApiConnectErrorMessage": "Не удалось подключиться к серверу Amiibo API. Служба может быть недоступна, или вам может потребоваться проверить, подключено ли ваше интернет-соединение к сети.", + "DialogProfileInvalidProfileErrorMessage": "Профиль {0} несовместим с текущей системой конфигурации ввода.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Профиль по умолчанию не может быть перезаписан", + "DialogProfileDeleteProfileTitle": "Удаление профиля", + "DialogProfileDeleteProfileMessage": "Это действие необратимо. Вы уверены, что хотите продолжить?", + "DialogWarning": "Внимание", + "DialogPPTCDeletionMessage": "Вы собираетесь удалить кэш PPTC для:\n\n{0}\n\nВы уверены, что хотите продолжить?", + "DialogPPTCDeletionErrorMessage": "Ошибка очистки кэша PPTC в {0}: {1}", + "DialogShaderDeletionMessage": "Вы собираетесь удалить кэш шейдеров для:\n\n{0}\n\nВы уверены, что хотите продолжить?", + "DialogShaderDeletionErrorMessage": "Ошибка очистки кэша шейдеров в {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx обнаружил ошибку", + "DialogInvalidTitleIdErrorMessage": "Ошибка пользовательского интерфейса: выбранная игра не имеет действительного идентификатора названия.", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Действительная системная прошивка не найдена в {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Установить прошивку {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Будет установлена версия системы {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nЭто заменит текущую версию системы {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nПродолжить?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Установка прошивки...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Версия системы {0} успешно установлена.", + "DialogUserProfileDeletionWarningMessage": "Если выбранный профиль будет удален, другие профили не будут открываться.", + "DialogUserProfileDeletionConfirmMessage": "Вы хотите удалить выбранный профиль", + "DialogControllerSettingsModifiedConfirmMessage": "Текущие настройки контроллера обновлены.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Вы хотите сохранить?", + "DialogLoadNcaErrorMessage": "{0}. Файл с ошибкой: {1}", + "DialogDlcNoDlcErrorMessage": "Указанный файл не содержит DLC для выбранной игры!", + "DialogPerformanceCheckLoggingEnabledMessage": "У вас включено ведение журнала отладки, предназначенное только для разработчиков.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить ведение журнала отладки. Вы хотите отключить ведение журнала отладки сейчас?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "У вас включен сброс шейдеров, который предназначен только для разработчиков.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Для оптимальной производительности рекомендуется отключить сброс шейдеров. Вы хотите отключить сброс шейдеров сейчас?", + "DialogLoadAppGameAlreadyLoadedMessage": "Игра уже загружена", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Пожалуйста, остановите эмуляцию или закройте эмулятор перед запуском другой игры.", + "DialogUpdateAddUpdateErrorMessage": "Указанный файл не содержит обновления для выбранного заголовка!", + "DialogSettingsBackendThreadingWarningTitle": "Предупреждение: многопоточность в бэкенде", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx необходимо перезапустить после изменения этой опции, чтобы она полностью применилась. В зависимости от вашей платформы вам может потребоваться вручную отключить собственную многопоточность вашего драйвера при использовании Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Функции & Улучшения", + "SettingsTabGraphicsBackendMultithreading": "Многопоточность графического бэкенда:", + "CommonAuto": "Автоматически", + "CommonOff": "Выключен", + "CommonOn": "Включен", + "InputDialogYes": "Да", + "InputDialogNo": "Нет", + "DialogProfileInvalidProfileNameErrorMessage": "Имя файла содержит недопустимые символы. Пожалуйста, попробуйте еще раз.", + "MenuBarOptionsPauseEmulation": "Пауза", + "MenuBarOptionsResumeEmulation": "Продолжить", + "AboutUrlTooltipMessage": "Нажмите, чтобы открыть веб-сайт Ryujinx в браузере по умолчанию.", + "AboutDisclaimerMessage": "Ryujinx никоим образом не связан ни с Nintendo™, ни с кем-либо из ее партнеров.", + "AboutAmiiboDisclaimerMessage": "Amiibo API (www.amiibo api.com) используется\n нашей эмуляции Amiibo.", + "AboutPatreonUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx Patreon в браузере по умолчанию.", + "AboutGithubUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx GitHub в браузере по умолчанию.", + "AboutDiscordUrlTooltipMessage": "Нажмите, чтобы открыть приглашение на сервер Ryujinx Discord в браузере по умолчанию.", + "AboutTwitterUrlTooltipMessage": "Нажмите, чтобы открыть страницу Ryujinx в Twitter в браузере по умолчанию.", + "AboutRyujinxAboutTitle": "О программе:", + "AboutRyujinxAboutContent": "Ryujinx — это эмулятор Nintendo Switch™.\nПожалуйста, поддержите нас на Patreon.\nУзнавайте все последние новости в нашем Twitter или Discord.\nРазработчики, заинтересованные в участии, могут узнать больше на нашем GitHub или в Discord.", + "AboutRyujinxMaintainersTitle": "Поддерживается:", + "AboutRyujinxMaintainersContentTooltipMessage": "Нажмите, чтобы открыть страницу Contributors в браузере по умолчанию.", + "AboutRyujinxSupprtersTitle": "Поддерживается на Patreon:", + "AmiiboSeriesLabel": "Серия Amiibo", + "AmiiboCharacterLabel": "Персонаж", + "AmiiboScanButtonLabel": "Сканировать", + "AmiiboOptionsShowAllLabel": "Показать все Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Хак: Использовать случайный тег Uuid", + "DlcManagerTableHeadingEnabledLabel": "Велючено", + "DlcManagerTableHeadingTitleIdLabel": "Идентификатор заголовка", + "DlcManagerTableHeadingContainerPathLabel": "Путь к контейнеру", + "DlcManagerTableHeadingFullPathLabel": "Полный путь", + "DlcManagerRemoveAllButton": "Убрать все", + "DlcManagerEnableAllButton": "Включить все", + "DlcManagerDisableAllButton": "Отключить все", + "MenuBarOptionsChangeLanguage": "Изменить язык", + "CommonSort": "Сортировать", + "CommonShowNames": "Показать названия", + "CommonFavorite": "Избранные", + "OrderAscending": "По возрастанию", + "OrderDescending": "По убыванию", + "SettingsTabGraphicsFeatures": "Функции", + "ErrorWindowTitle": "Окно ошибки", + "ToggleDiscordTooltip": "Включает или отключает Discord Rich Presenc", + "AddGameDirBoxTooltip": "Введите каталог игры, чтобы добавить его в список", + "AddGameDirTooltip": "AДобавить папку с игрой в список", + "RemoveGameDirTooltip": "Удалить выбранный каталог игры", + "CustomThemeCheckTooltip": "Включить или отключить пользовательские темы в графическом интерфейсе", + "CustomThemePathTooltip": "Путь к пользовательской теме графического интерфейса", + "CustomThemeBrowseTooltip": "Обзор пользовательской темы графического интерфейса", + "DockModeToggleTooltip": "Включить или отключить режим закрепления", + "DirectKeyboardTooltip": "Включить или отключить «поддержку прямого доступа к клавиатуре (HID)» (предоставляет играм доступ к клавиатуре как к устройству ввода текста)", + "DirectMouseTooltip": "Включить или отключить «поддержку прямого доступа к мыши (HID)» (предоставляет играм доступ к вашей мыши как указывающему устройству)", + "RegionTooltip": "Изменяет регион системы", + "LanguageTooltip": "Изменяет язык системы", + "TimezoneTooltip": "Изменяет часовой пояс системы", + "TimeTooltip": "Изменяет системное время", + "VSyncToggleTooltip": "Включает или отключает Вертикальную Синхронизацию", + "PptcToggleTooltip": "Включает или отключает PPTC", + "FsIntegrityToggleTooltip": "Включает проверку целостности файлов содержимого игры.", + "AudioBackendTooltip": "Изменяет аудио-бэкэнд", + "MemoryManagerTooltip": "Изменяет способ отображения и доступа к гостевой памяти. Сильно влияет на производительность эмулируемого процессора.", + "MemoryManagerSoftwareTooltip": "Использует таблицу страниц программного обеспечения для преобразования адресов. Самая высокая точность, но самая медленная производительность.", + "MemoryManagerHostTooltip": "Непосредственное отображение памяти в адресном пространстве хоста. Значительно более быстрая JIT-компиляция и выполнение.", + "MemoryManagerUnsafeTooltip": "Напрямую сопоставляет память, но не маскируйте адрес в гостевом адресном пространстве перед доступом. Быстрее, но ценой безопасности. Гостевое приложение может получить доступ к памяти из любой точки Ryujinx, поэтому в этом режиме запускайте только те программы, которым вы доверяете.", + "DRamTooltip": "Увеличивает объем памяти в эмулируемой системе с 4 ГБ до 6 ГБ.", + "IgnoreMissingServicesTooltip": "Включает или отключает параметр игнорирования отсутствующих служб", + "GraphicsBackendThreadingTooltip": "Включает многопоточность графического бэкенда", + "GalThreadingTooltip": "Выполняет команды графического бэкэнда во втором потоке. Обеспечивает многопоточность компиляции шейдеров во время выполнения, уменьшает заикание и повышает производительность драйверов без собственной поддержки многопоточности. Немного различается пиковая производительность на драйверах с многопоточностью. Ryujinx может потребоваться перезапустить, чтобы правильно отключить встроенную многопоточность драйвера, или вам может потребоваться сделать это вручную, чтобы получить максимальную производительность.", + "ShaderCacheToggleTooltip": "Включает или отключает кэш шейдеров", + "ResolutionScaleTooltip": "Масштаб разрешения, применяемый к применимым целям рендеринга", + "ResolutionScaleEntryTooltip": "Шкала разрешения с плавающей запятой, например 1,5. Неинтегральные весы с большей вероятностью вызовут проблемы или сбои.", + "AnisotropyTooltip": "Уровень анизотропной фильтрации (установите значение «Авто», чтобы использовать значение, запрошенное игрой)", + "AspectRatioTooltip": "Соотношение сторон, применяемое к окну рендерера.", + "ShaderDumpPathTooltip": "Путь дампа графических шейдеров", + "FileLogTooltip": "Включает или отключает ведение журнала в файл на диске", + "StubLogTooltip": "Включает печать сообщений журнала-заглушки", + "InfoLogTooltip": "Включает печать сообщений информационного журнала", + "WarnLogTooltip": "Включает печать сообщений журнала предупреждений", + "ErrorLogTooltip": "Включает печать сообщений журнала ошибок", + "TraceLogTooltip": "Выводит сообщения журнала трассировки в консоли. Не влияет на производительность.", + "GuestLogTooltip": "Включает печать сообщений гостевого журнала", + "FileAccessLogTooltip": "Включает печать сообщений журнала доступа к файлам", + "FSAccessLogModeTooltip": "Включает вывод журнала доступа к FS на консоль. Возможные режимы 0-3", + "DeveloperOptionTooltip": "Используйте с осторожностью", + "OpenGlLogLevel": "Требует включения соответствующих уровней ведения журнала", + "DebugLogTooltip": "Включает печать сообщений журнала отладки", + "LoadApplicationFileTooltip": "Загружает средство выбора файлов, чтобы выбрать файл, совместимый с Switch, для загрузки", + "LoadApplicationFolderTooltip": "Загружает средство выбора файлов, чтобы выбрать распакованное приложение, совместимое с Switch, для загрузки", + "OpenRyujinxFolderTooltip": "Открывает папку файловой системы Ryujinx. ", + "OpenRyujinxLogsTooltip": "Открывает папку, в которую записываются журналы", + "ExitTooltip": "Выходит из Ryujinx", + "OpenSettingsTooltip": "Открывает окно настроек", + "OpenProfileManagerTooltip": "Открывает окно диспетчера профилей пользователей", + "StopEmulationTooltip": "Останавливает эмуляцию текущей игры и вовращение к выбору игры", + "CheckUpdatesTooltip": "Проверяет наличие обновлений Ryujinx", + "OpenAboutTooltip": "Открывает окно «О программе»", + "GridSize": "Размер сетки", + "GridSizeTooltip": "Изменение размера элементов сетки", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Португальский язык (Бразилия)", + "AboutRyujinxContributorsButtonHeader": "Посмотреть всех участников", + "SettingsTabSystemAudioVolume": "Громкость: ", + "AudioVolumeTooltip": "Изменяет громкость звука", + "SettingsTabSystemEnableInternetAccess": "Включить гостевой доступ в Интернет", + "EnableInternetAccessTooltip": "Включает гостевой доступ в Интернет. Если этот параметр включен, приложение будет вести себя так, как если бы эмулированная консоль Switch была подключена к Интернету. Обратите внимание, что в некоторых случаях приложения могут по-прежнему получать доступ к Интернету, даже если эта опция отключена.", + "GameListContextMenuManageCheatToolTip": "Управление читами", + "GameListContextMenuManageCheat": "Управление читами", + "ControllerSettingsStickRange": "Диапазон:", + "DialogStopEmulationTitle": "Ryujinx - Остановить эмуляцию", + "DialogStopEmulationMessage": "Вы уверены, что хотите остановить эмуляцию?", + "SettingsTabCpu": "ЦП", + "SettingsTabAudio": "Аудио", + "SettingsTabNetwork": "Сеть", + "SettingsTabNetworkConnection": "Подключение к сети", + "SettingsTabCpuCache": "Кэш ЦП", + "SettingsTabCpuMemory": "Память ЦП", + "DialogUpdaterFlatpakNotSupportedMessage": "Пожалуйста, обновите Ryujinx через FlatHub.", + "UpdaterDisabledWarningTitle": "Updater Disabled!", + "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", + "ControllerSettingsRotate90": "Rotate 90° Clockwise", + "IconSize": "Icon Size", + "IconSizeTooltip": "Change the size of game icons", + "MenuBarOptionsShowConsole": "Открыть консоль", + "ShaderCachePurgeError": "Error purging shader cache at {0}: {1}", + "UserErrorNoKeys": "Keys not found", + "UserErrorNoFirmware": "Прошивка не найдена", + "UserErrorFirmwareParsingFailed": "Firmware parsing error", + "UserErrorApplicationNotFound": "Приложение не найдено", + "UserErrorUnknown": "Неизвестная ошибка", + "UserErrorUndefined": "Неопределенная ошибка", + "UserErrorNoKeysDescription": "Ryujinx was unable to find your 'prod.keys' file", + "UserErrorNoFirmwareDescription": "Ryujinx was unable to find any firmwares installed", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx was unable to parse the provided firmware. This is usually caused by outdated keys.", + "UserErrorApplicationNotFoundDescription": "Ryujinx couldn't find a valid application at the given path.", + "UserErrorUnknownDescription": "Произошла неизвестная ошибка!", + "UserErrorUndefinedDescription": "Произошла неизвестная ошибка! Такого не должно происходить. Пожалуйста, свяжитесь с разработчиками!", + "OpenSetupGuideMessage": "Open the Setup Guide", + "NoUpdate": "No Update", + "TitleUpdateVersionLabel": "Version {0} - {1}", + "RyujinxInfo": "Ryujinx - Info", + "RyujinxConfirm": "Ryujinx - Confirmation", + "FileDialogAllTypes": "Все типы", + "Never": "Никогда", + "SwkbdMinCharacters": "Must be at least {0} characters long", + "SwkbdMinRangeCharacters": "Must be {0}-{1} characters long", + "SoftwareKeyboard": "Software Keyboard", + "DialogControllerAppletMessagePlayerRange": "Application requests {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.", + "DialogControllerAppletMessage": "Application requests exactly {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.", + "DialogControllerAppletDockModeSet": "Docked mode set. Handheld is also invalid.\n\n", + "UpdaterRenaming": "Переименование старых файлов...", + "UpdaterRenameFailed": "Updater was unable to rename file: {0}", + "UpdaterAddingFiles": "Добавление новых файлов...", + "UpdaterExtracting": "Извлечение обновления...", + "UpdaterDownloading": "Загрузка обновления...", + "Game": "Игра", + "Docked": "Docked", + "Handheld": "Handheld", + "ConnectionError": "Connection Error.", + "AboutPageDeveloperListMore": "{0} and more...", + "ApiError": "Ошибка API.", + "LoadingHeading": "Loading {0}", + "CompilingPPTC": "Compiling PTC", + "CompilingShaders": "Компилируем шейдеры", + "AllKeyboards": "Все клавиатуры", + "OpenFileDialogTitle": "Select a supported file to open", + "OpenFolderDialogTitle": "Select a folder with an unpacked game", + "AllSupportedFormats": "Все поддерживаемые форматы", + "RyujinxUpdater": "Ryujinx Updater", + "SettingsTabHotkeys": "Keyboard Hotkeys", + "SettingsTabHotkeysHotkeys": "Keyboard Hotkeys", + "SettingsTabHotkeysToggleVsyncHotkey": "Toggle VSync:", + "SettingsTabHotkeysScreenshotHotkey": "Скриншот:", + "SettingsTabHotkeysShowUiHotkey": "Показать UI:", + "SettingsTabHotkeysPauseHotkey": "Pause:", + "SettingsTabHotkeysToggleMuteHotkey": "Mute:", + "ControllerMotionTitle": "Motion Control Settings", + "ControllerRumbleTitle": "Rumble Settings", + "SettingsSelectThemeFileDialogTitle": "Select Theme File", + "SettingsXamlThemeFile": "Xaml Theme File", + "AvatarWindowTitle": "Manage Accounts - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Unknown", + "Usage": "Usage", + "Writable": "Writable", + "SelectDlcDialogTitle": "Выберите файлы DLC", + "SelectUpdateDialogTitle": "Выберите файлы обновления", + "UserProfileWindowTitle": "User Profiles Manager", + "CheatWindowTitle": "Cheats Manager", + "DlcWindowTitle": "Downloadable Content Manager", + "UpdateWindowTitle": "Title Update Manager", + "CheatWindowHeading": "Cheats Available for {0} [{1}]", + "DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})", + "UserProfilesEditProfile": "Edit Selected", + "Cancel": "Cancel", + "Save": "Сохранить", + "Discard": "Discard", + "UserProfilesSetProfileImage": "Set Profile Image", + "UserProfileEmptyNameError": "Name is required", + "UserProfileNoImageError": "Profile image must be set", + "GameUpdateWindowHeading": "{0} Update(s) available for {1} ({2})", + "SettingsTabHotkeysResScaleUpHotkey": "Увеличить разрешение:", + "SettingsTabHotkeysResScaleDownHotkey": "Уменьшить разрешение:", + "UserProfilesName": "Name:", + "UserProfilesUserId": "User Id:", + "SettingsTabGraphicsBackend": "Graphics Backend", + "SettingsTabGraphicsBackendTooltip": "Graphics Backend to use", + "SettingsEnableTextureRecompression": "Включить пережатие текстур", + "SettingsEnableTextureRecompressionTooltip": "Сжимает некоторые текстуры для уменьшения использования видеопамяти.\n\nРекомендуется для ГП с 4 ГиБ видеопамяти и менее.\n\nЕсли не уверены, оставьте ВЫКЛ.", + "SettingsTabGraphicsPreferredGpu": "Preferred GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Select the graphics card that will be used with the Vulkan graphics backend.\n\nDoes not affect the GPU that OpenGL will use.\n\nSet to the GPU flagged as \"dGPU\" if unsure. If there isn't one, leave untouched.", + "SettingsAppRequiredRestartMessage": "Ryujinx Restart Required", + "SettingsGpuBackendRestartMessage": "Graphics Backend or GPU settings have been modified. This will require a restart to be applied", + "SettingsGpuBackendRestartSubMessage": "Do you want to restart now?", + "RyujinxUpdaterMessage": "Вы хотите обновить Ryujinx до последней версии?", + "SettingsTabHotkeysVolumeUpHotkey": "Увеличить громкость:", + "SettingsTabHotkeysVolumeDownHotkey": "Уменьшить громкость:", + "SettingsEnableMacroHLE": "Enable Macro HLE", + "SettingsEnableMacroHLETooltip": "High-level emulation of GPU Macro code.\n\nImproves performance, but may cause graphical glitches in some games.\n\nLeave ON if unsure.", + "VolumeShort": "Vol", + "UserProfilesManageSaves": "Manage Saves", + "DeleteUserSave": "Do you want to delete user save for this game?", + "IrreversibleActionNote": "Данное действие является необратимым.", + "SaveManagerHeading": "Manage Saves for {0}", + "SaveManagerTitle": "Save Manager", + "Name": "Название", + "Size": "Размер", + "Search": "Search", + "UserProfilesRecoverLostAccounts": "Recover Lost Accounts", + "Recover": "Recover", + "UserProfilesRecoverHeading": "Saves were found for the following accounts" +} diff --git a/src/Ryujinx.Ava/Assets/Locales/tr_TR.json b/src/Ryujinx.Ava/Assets/Locales/tr_TR.json new file mode 100644 index 00000000..decc3cfd --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/tr_TR.json @@ -0,0 +1,614 @@ +{ + "Language": "Türkçe", + "MenuBarFileOpenApplet": "Applet'i Aç", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Mii Editör Applet'ini Bağımsız Mod'da Aç", + "SettingsTabInputDirectMouseAccess": "Doğrudan Mouse Erişimi", + "SettingsTabSystemMemoryManagerMode": "Hafıza Yönetim Modu:", + "SettingsTabSystemMemoryManagerModeSoftware": "Yazılım", + "SettingsTabSystemMemoryManagerModeHost": "Host (hızlı)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host Unchecked (en hızlısı, tehlikeli)", + "MenuBarFile": "_Dosya", + "MenuBarFileOpenFromFile": "_Dosyadan Uygulama Aç", + "MenuBarFileOpenUnpacked": "_Sıkıştırılmamış Oyun Aç", + "MenuBarFileOpenEmuFolder": "Ryujinx Klasörünü aç", + "MenuBarFileOpenLogsFolder": "Logs Klasörünü aç", + "MenuBarFileExit": "_Çıkış", + "MenuBarOptions": "Seçenekler", + "MenuBarOptionsToggleFullscreen": "Tam Ekran Modunu Aç", + "MenuBarOptionsStartGamesInFullscreen": "Oyunları Tam Ekran Modunda Başlat", + "MenuBarOptionsStopEmulation": "Emülasyonu Durdur", + "MenuBarOptionsSettings": "_Seçenekler", + "MenuBarOptionsManageUserProfiles": "_Kullanıcı Profillerini Yönet", + "MenuBarActions": "_Eylemler", + "MenuBarOptionsSimulateWakeUpMessage": "Uyandırma Mesajı Simüle Et", + "MenuBarActionsScanAmiibo": "Bir Amiibo Tara", + "MenuBarTools": "_Araçlar", + "MenuBarToolsInstallFirmware": "Yazılım Yükle", + "MenuBarFileToolsInstallFirmwareFromFile": "XCI veya ZIP'ten Yazılım Yükle", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Bir Dizin Üzerinden Yazılım Yükle", + "MenuBarHelp": "Yardım", + "MenuBarHelpCheckForUpdates": "Güncellemeleri Denetle", + "MenuBarHelpAbout": "Hakkında", + "MenuSearch": "Ara...", + "GameListHeaderFavorite": "Favori", + "GameListHeaderIcon": "Simge", + "GameListHeaderApplication": "Oyun Adı", + "GameListHeaderDeveloper": "Geliştirici", + "GameListHeaderVersion": "Sürüm", + "GameListHeaderTimePlayed": "Oynama Süresi", + "GameListHeaderLastPlayed": "Son Oynama Tarihi", + "GameListHeaderFileExtension": "Dosya Uzantısı", + "GameListHeaderFileSize": "Dosya Boyutu", + "GameListHeaderPath": "Yol", + "GameListContextMenuOpenUserSaveDirectory": "Kullanıcı Kayıt Dosyası Dizinini Aç", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Uygulamanın Kullanıcı Kaydı'nın bulunduğu dizini açar", + "GameListContextMenuOpenDeviceSaveDirectory": "Kullanıcı Cihaz Dizinini Aç", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Uygulamanın Kullanıcı Cihaz Kaydı'nın bulunduğu dizini açar", + "GameListContextMenuOpenBcatSaveDirectory": "Kullanıcı BCAT Dizinini Aç", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Uygulamanın Kullanıcı BCAT Kaydı'nın bulunduğu dizini açar", + "GameListContextMenuManageTitleUpdates": "Oyun Güncellemelerini Yönet", + "GameListContextMenuManageTitleUpdatesToolTip": "Oyun Güncelleme Yönetim Penceresini Açar", + "GameListContextMenuManageDlc": "DLC'leri Yönet", + "GameListContextMenuManageDlcToolTip": "DLC yönetim penceresini açar", + "GameListContextMenuOpenModsDirectory": "Mod Dizinini Aç", + "GameListContextMenuOpenModsDirectoryToolTip": "Uygulamanın modlarının bulunduğu dizini açar", + "GameListContextMenuCacheManagement": "Önbellek Yönetimi", + "GameListContextMenuCacheManagementPurgePptc": "PPTC Yeniden Yapılandırmasını Başlat", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Oyunun bir sonraki açılışında PPTC'yi yeniden yapılandır", + "GameListContextMenuCacheManagementPurgeShaderCache": "Shader Önbelleğini Temizle", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Uygulamanın shader önbelleğini temizler", + "GameListContextMenuCacheManagementOpenPptcDirectory": "PPTC Dizinini Aç", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Uygulamanın PPTC Önbelleğinin bulunduğu dizini açar", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Shader Önbelleği Dizinini Aç", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Uygulamanın shader önbelleğinin bulunduğu dizini açar", + "GameListContextMenuExtractData": "Veriyi Ayıkla", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Uygulamanın geçerli yapılandırmasından ExeFS kısmını ayıkla (Güncellemeler dahil)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Uygulamanın geçerli yapılandırmasından RomFS kısmını ayıkla (Güncellemeler dahil)", + "GameListContextMenuExtractDataLogo": "Logo", + "GameListContextMenuExtractDataLogoToolTip": "Uygulamanın geçerli yapılandırmasından Logo kısmını ayıkla (Güncellemeler dahil)", + "StatusBarGamesLoaded": "{0}/{1} Oyun Yüklendi", + "StatusBarSystemVersion": "Sistem Sürümü: {0}", + "Settings": "Ayarlar", + "SettingsTabGeneral": "Kullancı Arayüzü", + "SettingsTabGeneralGeneral": "Genel", + "SettingsTabGeneralEnableDiscordRichPresence": "Discord Zengin İçerik'i Etkinleştir", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Her Açılışta Güncellemeleri Denetle", + "SettingsTabGeneralShowConfirmExitDialog": "\"Çıkışı Onayla\" Diyaloğunu Göster", + "SettingsTabGeneralHideCursorOnIdle": "Hareketsizlik durumunda imleci gizle", + "SettingsTabGeneralGameDirectories": "Oyun Dizinleri", + "SettingsTabGeneralAdd": "Ekle", + "SettingsTabGeneralRemove": "Kaldır", + "SettingsTabSystem": "Sistem", + "SettingsTabSystemCore": "Çekirdek", + "SettingsTabSystemSystemRegion": "Sistem Bölgesi:", + "SettingsTabSystemSystemRegionJapan": "Japonya", + "SettingsTabSystemSystemRegionUSA": "ABD", + "SettingsTabSystemSystemRegionEurope": "Avrupa", + "SettingsTabSystemSystemRegionAustralia": "Avustralya", + "SettingsTabSystemSystemRegionChina": "Çin", + "SettingsTabSystemSystemRegionKorea": "Kore", + "SettingsTabSystemSystemRegionTaiwan": "Tayvan", + "SettingsTabSystemSystemLanguage": "Sistem Dili:", + "SettingsTabSystemSystemLanguageJapanese": "Japonca", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Amerikan İngilizcesi", + "SettingsTabSystemSystemLanguageFrench": "Fransızca", + "SettingsTabSystemSystemLanguageGerman": "Almanca", + "SettingsTabSystemSystemLanguageItalian": "İtalyanca", + "SettingsTabSystemSystemLanguageSpanish": "İspanyolca", + "SettingsTabSystemSystemLanguageChinese": "Çince", + "SettingsTabSystemSystemLanguageKorean": "Korece", + "SettingsTabSystemSystemLanguageDutch": "Flemenkçe", + "SettingsTabSystemSystemLanguagePortuguese": "Portekizce", + "SettingsTabSystemSystemLanguageRussian": "Rusça", + "SettingsTabSystemSystemLanguageTaiwanese": "Tayvanca", + "SettingsTabSystemSystemLanguageBritishEnglish": "İngiliz İngilizcesi", + "SettingsTabSystemSystemLanguageCanadianFrench": "Kanada Fransızcası", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Latin Amerika İspanyolcası", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Basitleştirilmiş Çince", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Geleneksel Çince", + "SettingsTabSystemSystemTimeZone": "Sistem Saat Dilimi:", + "SettingsTabSystemSystemTime": "Sistem Saati:", + "SettingsTabSystemEnableVsync": "VSync", + "SettingsTabSystemEnablePptc": "PPTC (Profilli Sürekli Çeviri Önbelleği)", + "SettingsTabSystemEnableFsIntegrityChecks": "FS Bütünlük Kontrolleri", + "SettingsTabSystemAudioBackend": "Ses Motoru:", + "SettingsTabSystemAudioBackendDummy": "Yapay", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Hack'ler", + "SettingsTabSystemHacksNote": " (dengesizlik oluşturabilir)", + "SettingsTabSystemExpandDramSize": "Alternatif bellek düzeni kullan (Geliştirici)", + "SettingsTabSystemIgnoreMissingServices": "Eksik Servisleri Görmezden Gel", + "SettingsTabGraphics": "Grafikler", + "SettingsTabGraphicsAPI": "Grafikler API", + "SettingsTabGraphicsEnableShaderCache": "Shader Önbelleğini Etkinleştir", + "SettingsTabGraphicsAnisotropicFiltering": "Eşyönsüz Doku Süzmesi:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Otomatik", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Çözünürlük Ölçeği:", + "SettingsTabGraphicsResolutionScaleCustom": "Özel (Tavsiye Edilmez)", + "SettingsTabGraphicsResolutionScaleNative": "Yerel (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "En-Boy Oranı:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Pencereye Sığdırmak İçin Genişlet", + "SettingsTabGraphicsDeveloperOptions": "Geliştirici Seçenekleri", + "SettingsTabGraphicsShaderDumpPath": "Grafik Shader Döküm Yolu:", + "SettingsTabLogging": "Loglama", + "SettingsTabLoggingLogging": "Loglama", + "SettingsTabLoggingEnableLoggingToFile": "Logları Dosyaya Kaydetmeyi Etkinleştir", + "SettingsTabLoggingEnableStubLogs": "Stub Loglarını Etkinleştir", + "SettingsTabLoggingEnableInfoLogs": "Bilgi Loglarını Etkinleştir", + "SettingsTabLoggingEnableWarningLogs": "Uyarı Loglarını Etkinleştir", + "SettingsTabLoggingEnableErrorLogs": "Hata Loglarını Etkinleştir", + "SettingsTabLoggingEnableTraceLogs": "Trace Loglarını Etkinleştir", + "SettingsTabLoggingEnableGuestLogs": "Guest Loglarını Etkinleştir", + "SettingsTabLoggingEnableFsAccessLogs": "Fs Erişim Loglarını Etkinleştir", + "SettingsTabLoggingFsGlobalAccessLogMode": "Fs Evrensel Erişim Log Modu:", + "SettingsTabLoggingDeveloperOptions": "Geliştirici Seçenekleri (UYARI: Performansı düşürecektir)", + "SettingsTabLoggingGraphicsBackendLogLevel": "Grafik Arka Uç Günlük Düzeyi", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Hiçbiri", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Hata", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Yavaşlamalar", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Hepsi", + "SettingsTabLoggingEnableDebugLogs": "Hata Ayıklama Loglarını Etkinleştir", + "SettingsTabInput": "Giriş Yöntemi", + "SettingsTabInputEnableDockedMode": "Docked Modu Etkinleştir", + "SettingsTabInputDirectKeyboardAccess": "Doğrudan Klavye Erişimi", + "SettingsButtonSave": "Kaydet", + "SettingsButtonClose": "Kapat", + "SettingsButtonOk": "Tamam", + "SettingsButtonCancel": "İptal", + "SettingsButtonApply": "Uygula", + "ControllerSettingsPlayer": "Oyuncu", + "ControllerSettingsPlayer1": "Oyuncu 1", + "ControllerSettingsPlayer2": "Oyuncu 2", + "ControllerSettingsPlayer3": "Oyuncu 3", + "ControllerSettingsPlayer4": "Oyuncu 4", + "ControllerSettingsPlayer5": "Oyuncu 5", + "ControllerSettingsPlayer6": "Oyuncu 6", + "ControllerSettingsPlayer7": "Oyuncu 7", + "ControllerSettingsPlayer8": "Oyuncu 8", + "ControllerSettingsHandheld": "Portatif Mod", + "ControllerSettingsInputDevice": "Giriş Cihazı", + "ControllerSettingsRefresh": "Yenile", + "ControllerSettingsDeviceDisabled": "Devre Dışı", + "ControllerSettingsControllerType": "Kontrolcü Tipi", + "ControllerSettingsControllerTypeHandheld": "Portatif Mod", + "ControllerSettingsControllerTypeProController": "Profesyonel Denetleyici", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon Çifti", + "ControllerSettingsControllerTypeJoyConLeft": "JoyCon Sol", + "ControllerSettingsControllerTypeJoyConRight": "JoyCon Sağ", + "ControllerSettingsProfile": "Profil", + "ControllerSettingsProfileDefault": "Varsayılan", + "ControllerSettingsLoad": "Yükle", + "ControllerSettingsAdd": "Ekle", + "ControllerSettingsRemove": "Kaldır", + "ControllerSettingsButtons": "Tuşlar", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Yön Tuşları", + "ControllerSettingsDPadUp": "Yukarı", + "ControllerSettingsDPadDown": "Aşağı", + "ControllerSettingsDPadLeft": "Sol", + "ControllerSettingsDPadRight": "Sağ", + "ControllerSettingsLStick": "Sol Analog", + "ControllerSettingsLStickButton": "Tuş", + "ControllerSettingsLStickUp": "Yukarı", + "ControllerSettingsLStickDown": "Aşağı", + "ControllerSettingsLStickLeft": "Sol", + "ControllerSettingsLStickRight": "Sağ", + "ControllerSettingsLStickStick": "Analog", + "ControllerSettingsLStickInvertXAxis": "X Eksenini Tersine Çevir", + "ControllerSettingsLStickInvertYAxis": "Y Eksenini Tersine Çevir", + "ControllerSettingsLStickDeadzone": "Ölü Bölge:", + "ControllerSettingsRStick": "Sağ Analog", + "ControllerSettingsRStickButton": "Tuş", + "ControllerSettingsRStickUp": "Yukarı", + "ControllerSettingsRStickDown": "Aşağı", + "ControllerSettingsRStickLeft": "Sol", + "ControllerSettingsRStickRight": "Sağ", + "ControllerSettingsRStickStick": "Analog", + "ControllerSettingsRStickInvertXAxis": "X Eksenini Tersine Çevir", + "ControllerSettingsRStickInvertYAxis": "Y Eksenini Tersine Çevir", + "ControllerSettingsRStickDeadzone": "Ölü Bölge:", + "ControllerSettingsTriggersLeft": "Tetikler Sol", + "ControllerSettingsTriggersRight": "Tetikler Sağ", + "ControllerSettingsTriggersButtonsLeft": "Tetik Tuşları Sol", + "ControllerSettingsTriggersButtonsRight": "Tetik Tuşları Sağ", + "ControllerSettingsTriggers": "Tetikler", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Tuşlar Sol", + "ControllerSettingsExtraButtonsRight": "Tuşlar Sağ", + "ControllerSettingsMisc": "Diğer", + "ControllerSettingsTriggerThreshold": "Tetik Eşiği:", + "ControllerSettingsMotion": "Hareket", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "CemuHook uyumlu hareket kullan", + "ControllerSettingsMotionControllerSlot": "Kontrolcü Yuvası:", + "ControllerSettingsMotionMirrorInput": "Girişi Aynala", + "ControllerSettingsMotionRightJoyConSlot": "Sağ JoyCon Yuvası:", + "ControllerSettingsMotionServerHost": "Sunucu Sahibi:", + "ControllerSettingsMotionGyroSensitivity": "Gyro Hassasiyeti:", + "ControllerSettingsMotionGyroDeadzone": "Gyro Ölü Bölgesi:", + "ControllerSettingsSave": "Kaydet", + "ControllerSettingsClose": "Kapat", + "UserProfilesSelectedUserProfile": "Seçili Kullanıcı Profili:", + "UserProfilesSaveProfileName": "Profil İsmini Kaydet", + "UserProfilesChangeProfileImage": "Profil Resmini Değiştir", + "UserProfilesAvailableUserProfiles": "Mevcut Kullanıcı Profilleri:", + "UserProfilesAddNewProfile": "Yeni Profil Ekle", + "UserProfilesDelete": "Sil", + "UserProfilesClose": "Kapat", + "ProfileImageSelectionTitle": "Profil Resmi Seçimi", + "ProfileImageSelectionHeader": "Profil Resmi Seç", + "ProfileImageSelectionNote": "Özel bir profil resmi içeri aktarabilir veya sistem avatarlarından birini seçebilirsiniz", + "ProfileImageSelectionImportImage": "Resim İçeri Aktar", + "ProfileImageSelectionSelectAvatar": "Yazılım Avatarı Seç", + "InputDialogTitle": "Giriş Yöntemi Diyaloğu", + "InputDialogOk": "Tamam", + "InputDialogCancel": "İptal", + "InputDialogAddNewProfileTitle": "Profil İsmini Seç", + "InputDialogAddNewProfileHeader": "Lütfen Bir Profil İsmi Girin", + "InputDialogAddNewProfileSubtext": "(Maksimum Uzunluk: {0})", + "AvatarChoose": "Seç", + "AvatarSetBackgroundColor": "Arka Plan Rengi Ayarla", + "AvatarClose": "Kapat", + "ControllerSettingsLoadProfileToolTip": "Profil Yükle", + "ControllerSettingsAddProfileToolTip": "Profil Ekle", + "ControllerSettingsRemoveProfileToolTip": "Profili Kaldır", + "ControllerSettingsSaveProfileToolTip": "Profili Kaydet", + "MenuBarFileToolsTakeScreenshot": "Ekran Görüntüsü Al", + "MenuBarFileToolsHideUi": "Arayüzü Gizle", + "GameListContextMenuToggleFavorite": "Favori Ayarla", + "GameListContextMenuToggleFavoriteToolTip": "Oyunu Favorilere Ekle/Çıkar", + "SettingsTabGeneralTheme": "Tema", + "SettingsTabGeneralThemeCustomTheme": "Özel Tema Yolu", + "SettingsTabGeneralThemeBaseStyle": "Temel Stil", + "SettingsTabGeneralThemeBaseStyleDark": "Karanlık", + "SettingsTabGeneralThemeBaseStyleLight": "Aydınlık", + "SettingsTabGeneralThemeEnableCustomTheme": "Özel Tema Etkinleştir", + "ButtonBrowse": "Göz At", + "ControllerSettingsConfigureGeneral": "Ayarla", + "ControllerSettingsRumble": "Titreşim", + "ControllerSettingsRumbleStrongMultiplier": "Güçlü Titreşim Çoklayıcı", + "ControllerSettingsRumbleWeakMultiplier": "Zayıf Titreşim Seviyesi", + "DialogMessageSaveNotAvailableMessage": "{0} [{1:x16}] için kayıt verisi bulunamadı", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Bu oyun için kayıt verisi oluşturmak ister misiniz?", + "DialogConfirmationTitle": "Ryujinx - Onay", + "DialogUpdaterTitle": "Ryujinx - Güncelleyici", + "DialogErrorTitle": "Ryujinx - Hata", + "DialogWarningTitle": "Ryujinx - Uyarı", + "DialogExitTitle": "Ryujinx - Çıkış", + "DialogErrorMessage": "Ryujinx bir hata ile karşılaştı", + "DialogExitMessage": "Ryujinx'i kapatmak istediğinizden emin misiniz?", + "DialogExitSubMessage": "Kaydedilmeyen bütün veriler kaybedilecek!", + "DialogMessageCreateSaveErrorMessage": "Belirtilen kayıt verisi oluşturulurken bir hata oluştu: {0}", + "DialogMessageFindSaveErrorMessage": "Belirtilen kayıt verisi bulunmaya çalışırken hata: {0}", + "FolderDialogExtractTitle": "İçine ayıklanacak klasörü seç", + "DialogNcaExtractionMessage": "{1} den {0} kısmı ayıklanıyor...", + "DialogNcaExtractionTitle": "Ryujinx - NCA Kısmı Ayıklayıcısı", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Ayıklama hatası. Ana NCA seçilen dosyada bulunamadı.", + "DialogNcaExtractionCheckLogErrorMessage": "Ayıklama hatası. Ek bilgi için kayıt dosyasını okuyun.", + "DialogNcaExtractionSuccessMessage": "Ayıklama başarıyla tamamlandı.", + "DialogUpdaterConvertFailedMessage": "Güncel Ryujinx sürümü dönüştürülemedi.", + "DialogUpdaterCancelUpdateMessage": "Güncelleme iptal ediliyor!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Zaten Ryujinx'in en güncel sürümünü kullanıyorsunuz!", + "DialogUpdaterFailedToGetVersionMessage": "GitHub tarafından sürüm bilgileri alınırken bir hata oluştu. Eğer yeni sürüm için hazırlıklar yapılıyorsa bu hatayı almanız olasıdır. Lütfen birkaç dakika sonra tekrar deneyiniz.", + "DialogUpdaterConvertFailedGithubMessage": "Github Release'den alınan Ryujinx sürümü dönüştürülemedi.", + "DialogUpdaterDownloadingMessage": "Güncelleme İndiriliyor...", + "DialogUpdaterExtractionMessage": "Güncelleme Ayıklanıyor...", + "DialogUpdaterRenamingMessage": "Güncelleme Yeniden Adlandırılıyor...", + "DialogUpdaterAddingFilesMessage": "Yeni Güncelleme Ekleniyor...", + "DialogUpdaterCompleteMessage": "Güncelleme Tamamlandı!", + "DialogUpdaterRestartMessage": "Ryujinx'i şimdi yeniden başlatmak istiyor musunuz?", + "DialogUpdaterArchNotSupportedMessage": "Sistem mimariniz desteklenmemektedir!", + "DialogUpdaterArchNotSupportedSubMessage": "(Sadece x64 sistemleri desteklenmektedir!)", + "DialogUpdaterNoInternetMessage": "İnternete bağlı değilsiniz!", + "DialogUpdaterNoInternetSubMessage": "Lütfen aktif bir internet bağlantınız olduğunu kontrol edin!", + "DialogUpdaterDirtyBuildMessage": "Ryujinx'in Dirty build'lerini güncelleyemezsiniz!", + "DialogUpdaterDirtyBuildSubMessage": "Desteklenen bir sürüm için lütfen Ryujinx'i https://ryujinx.org/ sitesinden indirin.", + "DialogRestartRequiredMessage": "Yeniden Başlatma Gerekli", + "DialogThemeRestartMessage": "Tema kaydedildi. Temayı uygulamak için yeniden başlatma gerekiyor.", + "DialogThemeRestartSubMessage": "Yeniden başlatmak ister misiniz", + "DialogFirmwareInstallEmbeddedMessage": "Bu oyunun içine gömülü olan yazılımı yüklemek ister misiniz? (Firmware {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Yüklü firmware bulunamadı ancak Ryujinx sağlanan oyundan {0} firmware sürümünü yükledi.\nEmülatör şimdi başlatılacak.", + "DialogFirmwareNoFirmwareInstalledMessage": "Yazılım Yüklü Değil", + "DialogFirmwareInstalledMessage": "Yazılım {0} yüklendi", + "DialogOpenSettingsWindowLabel": "Seçenekler Penceresini Aç", + "DialogControllerAppletTitle": "Kontrolcü Applet'i", + "DialogMessageDialogErrorExceptionMessage": "Mesaj diyaloğu gösterilirken hata: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Mesaj diyaloğu gösterilirken hata: {0}", + "DialogErrorAppletErrorExceptionMessage": "Applet diyaloğu gösterilirken hata: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nBu hatayı düzeltmek adına daha fazla bilgi için kurulum kılavuzumuzu takip edin.", + "DialogUserErrorDialogTitle": "Ryujinx Hatası ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "API'dan bilgi alırken bir hata oluştu.", + "DialogAmiiboApiConnectErrorMessage": "Amiibo API sunucusuna bağlanılamadı. Sunucu çevrimdışı olabilir veya uygun bir internet bağlantınızın olduğunu kontrol etmeniz gerekebilir.", + "DialogProfileInvalidProfileErrorMessage": "Profil {0} güncel giriş konfigürasyon sistemi ile uyumlu değil.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Varsayılan Profil'in üstüne yazılamaz", + "DialogProfileDeleteProfileTitle": "Profil Siliniyor", + "DialogProfileDeleteProfileMessage": "Bu eylem geri döndürülemez, devam etmek istediğinizden emin misiniz?", + "DialogWarning": "Uyarı", + "DialogPPTCDeletionMessage": "Belirtilen PPTC cache silinecek :\n\n{0}\n\nDevam etmek istediğinizden emin misiniz?", + "DialogPPTCDeletionErrorMessage": "Belirtilen PPTC cache temizlenirken hata {0}: {1}", + "DialogShaderDeletionMessage": "Belirtilen Shader cache silinecek :\n\n{0}\n\nDevam etmek istediğinizden emin misiniz?", + "DialogShaderDeletionErrorMessage": "Belirtilen Shader cache temizlenirken hata {0}: {1}", + "DialogRyujinxErrorMessage": "Ryujinx bir hata ile karşılaştı", + "DialogInvalidTitleIdErrorMessage": "Arayüz hatası: Seçilen oyun geçerli bir title ID'ye sahip değil", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "{0} da geçerli bir sistem firmware'i bulunamadı.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Firmware {0} Yükle", + "DialogFirmwareInstallerFirmwareInstallMessage": "Sistem sürümü {0} yüklenecek.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nBu şimdiki sistem sürümünün yerini alacak {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nDevam etmek istiyor musunuz?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Firmware yükleniyor...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Sistem sürümü {0} başarıyla yüklendi.", + "DialogUserProfileDeletionWarningMessage": "Seçilen profil silinirse kullanılabilen başka profil kalmayacak", + "DialogUserProfileDeletionConfirmMessage": "Seçilen profili silmek istiyor musunuz", + "DialogControllerSettingsModifiedConfirmMessage": "Güncel kontrolcü seçenekleri güncellendi.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Kaydetmek istiyor musunuz?", + "DialogLoadNcaErrorMessage": "{0}. Hatalı Dosya: {1}", + "DialogDlcNoDlcErrorMessage": "Belirtilen dosya seçilen oyun için DLC içermiyor!", + "DialogPerformanceCheckLoggingEnabledMessage": "Sadece geliştiriler için dizayn edilen Trace Loglama seçeneği etkin.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "En iyi performans için trace loglama'nın devre dışı bırakılması tavsiye edilir. Trace loglama seçeneğini şimdi devre dışı bırakmak ister misiniz?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Sadece geliştiriler için dizayn edilen Shader Dumping seçeneği etkin.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "En iyi performans için Shader Dumping'in devre dışı bırakılması tavsiye edilir. Shader Dumping seçeneğini şimdi devre dışı bırakmak ister misiniz?", + "DialogLoadAppGameAlreadyLoadedMessage": "Bir oyun zaten yüklendi", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Lütfen yeni bir oyun açmadan önce emülasyonu durdurun veya emülatörü kapatın.", + "DialogUpdateAddUpdateErrorMessage": "Belirtilen dosya seçilen oyun için güncelleme içermiyor!", + "DialogSettingsBackendThreadingWarningTitle": "Uyarı - Backend Threading", + "DialogSettingsBackendThreadingWarningMessage": "Bu seçeneğin tamamen uygulanması için Ryujinx'in kapatıp açılması gerekir. Kullandığınız işletim sistemine bağlı olarak, Ryujinx'in multithreading'ini kullanırken driver'ınızın multithreading seçeneğini kapatmanız gerekebilir.", + "SettingsTabGraphicsFeaturesOptions": "Özellikler", + "SettingsTabGraphicsBackendMultithreading": "Grafik Backend Multithreading:", + "CommonAuto": "Otomatik", + "CommonOff": "Kapalı", + "CommonOn": "Açık", + "InputDialogYes": "Evet", + "InputDialogNo": "Hayır", + "DialogProfileInvalidProfileNameErrorMessage": "Dosya adı geçersiz karakter içeriyor. Lütfen tekrar deneyin.", + "MenuBarOptionsPauseEmulation": "Durdur", + "MenuBarOptionsResumeEmulation": "Devam Et", + "AboutUrlTooltipMessage": "Ryujinx'in websitesini varsayılan tarayıcınızda açmak için tıklayın.", + "AboutDisclaimerMessage": "Ryujinx, Nintendo™ veya ortaklarıyla herhangi bir şekilde bağlantılı değildir.", + "AboutAmiiboDisclaimerMessage": "Amiibo emülasyonumuzda \nAmiiboAPI (www.amiiboapi.com) kullanılmaktadır.", + "AboutPatreonUrlTooltipMessage": "Ryujinx'in Patreon sayfasını varsayılan tarayıcınızda açmak için tıklayın.", + "AboutGithubUrlTooltipMessage": "Ryujinx'in GitHub sayfasını varsayılan tarayıcınızda açmak için tıklayın.", + "AboutDiscordUrlTooltipMessage": "Varsayılan tarayıcınızda Ryujinx'in Discord'una bir davet açmak için tıklayın.", + "AboutTwitterUrlTooltipMessage": "Ryujinx'in Twitter sayfasını varsayılan tarayıcınızda açmak için tıklayın.", + "AboutRyujinxAboutTitle": "Hakkında:", + "AboutRyujinxAboutContent": "Ryujinx bir Nintendo Switch™ emülatörüdür.\nLütfen bizi Patreon'da destekleyin.\nEn son haberleri Twitter veya Discord'umuzdan alın.\nKatkıda bulunmak isteyen geliştiriciler GitHub veya Discord üzerinden daha fazla bilgi edinebilir.", + "AboutRyujinxMaintainersTitle": "Geliştiriciler:", + "AboutRyujinxMaintainersContentTooltipMessage": "Katkıda bulunanlar sayfasını varsayılan tarayıcınızda açmak için tıklayın.", + "AboutRyujinxSupprtersTitle": "Patreon Destekleyicileri:", + "AmiiboSeriesLabel": "Amiibo Serisi", + "AmiiboCharacterLabel": "Karakter", + "AmiiboScanButtonLabel": "Tarat", + "AmiiboOptionsShowAllLabel": "Tüm Amiibo'ları Göster", + "AmiiboOptionsUsRandomTagLabel": "Hack: Rastgele bir Uuid kullan", + "DlcManagerTableHeadingEnabledLabel": "Etkin", + "DlcManagerTableHeadingTitleIdLabel": "Başlık ID", + "DlcManagerTableHeadingContainerPathLabel": "Container Yol", + "DlcManagerTableHeadingFullPathLabel": "Tam Yol", + "DlcManagerRemoveAllButton": "Tümünü kaldır", + "DlcManagerEnableAllButton": "Tümünü Aktif Et", + "DlcManagerDisableAllButton": "Tümünü Devre Dışı Bırak", + "MenuBarOptionsChangeLanguage": "Dili Değiştir", + "CommonSort": "Sırala", + "CommonShowNames": "İsimleri Göster", + "CommonFavorite": "Favori", + "OrderAscending": "Artan", + "OrderDescending": "Azalan", + "SettingsTabGraphicsFeatures": "Özellikler & İyileştirmeler", + "ErrorWindowTitle": "Hata Penceresi", + "ToggleDiscordTooltip": "Ryujinx'i \"şimdi oynanıyor\" Discord aktivitesinde göstermeyi veya göstermemeyi seçin", + "AddGameDirBoxTooltip": "Listeye eklemek için oyun dizini seçin", + "AddGameDirTooltip": "Listeye oyun dizini ekle", + "RemoveGameDirTooltip": "Seçili oyun dizinini kaldır", + "CustomThemeCheckTooltip": "Emülatör pencerelerinin görünümünü değiştirmek için özel bir Avalonia teması kullan", + "CustomThemePathTooltip": "Özel arayüz temasının yolu", + "CustomThemeBrowseTooltip": "Özel arayüz teması için göz at", + "DockModeToggleTooltip": "Docked modu emüle edilen sistemin yerleşik Nintendo Switch gibi davranmasını sağlar. Bu çoğu oyunda grafik kalitesini arttırır. Diğer yandan, bu seçeneği devre dışı bırakmak emüle edilen sistemin elde Ninendo Switch gibi davranmasını sağlayıp grafik kalitesini düşürür.\n\nDocked modu kullanmayı düşünüyorsanız 1. Oyuncu kontrollerini; Handheld modunu kullanmak istiyorsanız Handheld kontrollerini konfigüre edin.\n\nEmin değilseniz aktif halde bırakın.", + "DirectKeyboardTooltip": "Doğrudan Klavye Erişimi (HID) desteği. Oyunların klavyenizi metin giriş cihazı olarak kullanmasını sağlar.", + "DirectMouseTooltip": "Doğrudan Fare Erişimi (HID) desteği. Oyunların farenizi işaret aygıtı olarak kullanmasını sağlar.", + "RegionTooltip": "Sistem Bölgesini Değiştir", + "LanguageTooltip": "Sistem Dilini Değiştir", + "TimezoneTooltip": "Sistem Saat Dilimini Değiştir", + "TimeTooltip": "Sistem Saatini Değiştir", + "VSyncToggleTooltip": "Emüle edilen konsolun Dikey Senkronizasyonu. Çoğu oyun için kare sınırlayıcı işlevi görür, bu seçeneği devre dışı bırakmak bazı oyunların normalden yüksek hızda çalışmasını ve yükleme ekranlarının daha uzun sürmesini veya sıkışıp kalmasını sağlar.\n\nTercih ettiğiniz bir kısayol ile oyun içindeyken etkinleştirilip devre dışı bırakılabilir. Bu seçeneği devre dışı bırakmayı düşünüyorsanız bir kısayol atamanızı öneririz.\n\nEmin değilseniz aktif halde bırakın.", + "PptcToggleTooltip": "Çevrilen JIT fonksiyonlarını oyun her açıldığında çevrilmek zorunda kalmaması için kaydeder.\n\nTeklemeyi azaltır ve ilk açılıştan sonra oyunların ilk açılış süresini ciddi biçimde hızlandırır.\n\nEmin değilseniz aktif halde bırakın.", + "FsIntegrityToggleTooltip": "Oyun açarken hatalı dosyaların olup olmadığını kontrol eder, ve hatalı dosya bulursa log dosyasında hash hatası görüntüler.\n\nPerformansa herhangi bir etkisi yoktur ve sorun gidermeye yardımcı olur.\n\nEmin değilseniz aktif halde bırakın.", + "AudioBackendTooltip": "Ses çıkış motorunu değiştirir.\n\nSDL2 tercih edilen seçenektir, OpenAL ve SoundIO ise alternatif olarak kullanılabilir. Dummy seçeneğinde ses çıkışı olmayacaktır.\n\nEmin değilseniz SDL2 seçeneğine ayarlayın.", + "MemoryManagerTooltip": "Guest hafızasının nasıl tahsis edilip erişildiğini değiştirir. Emüle edilen CPU performansını ciddi biçimde etkiler.\n\nEmin değilseniz HOST UNCHECKED seçeneğine ayarlayın.", + "MemoryManagerSoftwareTooltip": "Adres çevirisi için bir işlemci sayfası kullanır. En yüksek doğruluğu ve en yavaş performansı sunar.", + "MemoryManagerHostTooltip": "Hafızayı doğrudan host adres aralığında tahsis eder. Çok daha hızlı JIT derleme ve işletimi sunar.", + "MemoryManagerUnsafeTooltip": "Hafızayı doğrudan tahsis eder, ancak host aralığına erişimden önce adresi maskelemez. Daha iyi performansa karşılık emniyetten ödün verir. Misafir uygulama Ryujinx içerisinden istediği hafızaya erişebilir, bu sebeple bu seçenek ile sadece güvendiğiniz uygulamaları çalıştırın.", + "DRamTooltip": "Emüle edilen sistem hafızasını 4GiB'dan 6GiB'a yükseltir.\n\nBu seçenek yalnızca yüksek çözünürlük doku paketleri veya 4k çözünürlük modları için kullanılır. Performansı artırMAZ!\n\nEmin değilseniz devre dışı bırakın.", + "IgnoreMissingServicesTooltip": "Henüz programlanmamış Horizon işletim sistemi servislerini görmezden gelir. Bu seçenek belirli oyunların açılırken çökmesinin önüne geçmeye yardımcı olabilir.\n\nEmin değilseniz devre dışı bırakın.", + "GraphicsBackendThreadingTooltip": "Grafik arka uç komutlarını ikinci bir iş parçacığında işletir.\n\nKendi multithreading desteği olmayan sürücülerde shader derlemeyi hızlandırıp performansı artırır. Multithreading desteği olan sürücülerde çok az daha iyi performans sağlar.\n\nEmin değilseniz Otomatik seçeneğine ayarlayın.", + "GalThreadingTooltip": "Grafik arka uç komutlarını ikinci bir iş parçacığında işletir.\n\nKendi multithreading desteği olmayan sürücülerde shader derlemeyi hızlandırıp performansı artırır. Multithreading desteği olan sürücülerde çok az daha iyi performans sağlar.\n\nEmin değilseniz Otomatik seçeneğine ayarlayın.", + "ShaderCacheToggleTooltip": "Sonraki çalışmalarda takılmaları engelleyen bir gölgelendirici disk önbelleğine kaydeder.", + "ResolutionScaleTooltip": "Uygulanabilir grafik hedeflerine uygulanan çözünürlük ölçeği", + "ResolutionScaleEntryTooltip": "Küsüratlı çözünürlük ölçeği, 1.5 gibi. Küsüratlı ölçekler hata oluşturmaya ve çökmeye daha yatkındır.", + "AnisotropyTooltip": "Eşyönsüz doku süzmesi seviyesi (Oyun tarafından istenen değeri kullanmak için Otomatik seçeneğine ayarlayın)", + "AspectRatioTooltip": "Grafik penceresine uygulanan en-boy oranı.", + "ShaderDumpPathTooltip": "Grafik Shader Döküm Yolu", + "FileLogTooltip": "Konsol loglarını diskte bir log dosyasına kaydeder. Performansı etkilemez.", + "StubLogTooltip": "Stub log mesajlarını konsola yazdırır. Performansı etkilemez.", + "InfoLogTooltip": "Bilgi log mesajlarını konsola yazdırır. Performansı etkilemez.", + "WarnLogTooltip": "Uyarı log mesajlarını konsola yazdırır. Performansı etkilemez.", + "ErrorLogTooltip": "Hata log mesajlarını konsola yazdırır. Performansı etkilemez.", + "TraceLogTooltip": "Trace log mesajlarını konsola yazdırır. Performansı etkilemez.", + "GuestLogTooltip": "Guest log mesajlarını konsola yazdırır. Performansı etkilemez.", + "FileAccessLogTooltip": "Dosya sistemi erişim log mesajlarını konsola yazdırır.", + "FSAccessLogModeTooltip": "Konsola FS erişim loglarının yazılmasını etkinleştirir. Kullanılabilir modlar 0-3'tür", + "DeveloperOptionTooltip": "Dikkatli kullanın", + "OpenGlLogLevel": "Uygun log seviyesinin aktif olmasını gerektirir", + "DebugLogTooltip": "Debug log mesajlarını konsola yazdırır.\n\nBu seçeneği yalnızca geliştirici üyemiz belirtirse aktifleştirin, çünkü bu seçenek log dosyasını okumayı zorlaştırır ve emülatörün performansını düşürür.", + "LoadApplicationFileTooltip": "Switch ile uyumlu bir dosya yüklemek için dosya tarayıcısını açar", + "LoadApplicationFolderTooltip": "Switch ile uyumlu ayrıştırılmamış bir uygulama yüklemek için dosya tarayıcısını açar", + "OpenRyujinxFolderTooltip": "Ryujinx dosya sistem klasörünü açar", + "OpenRyujinxLogsTooltip": "Log dosyalarının bulunduğu klasörü açar", + "ExitTooltip": "Ryujinx'ten çıkış yapmayı sağlar", + "OpenSettingsTooltip": "Seçenekler penceresini açar", + "OpenProfileManagerTooltip": "Kullanıcı profil yöneticisi penceresini açar", + "StopEmulationTooltip": "Oynanmakta olan oyunun emülasyonunu durdurup oyun seçimine geri döndürür", + "CheckUpdatesTooltip": "Ryujinx güncellemelerini denetlemeyi sağlar", + "OpenAboutTooltip": "Hakkında penceresini açar", + "GridSize": "Öge Boyutu", + "GridSizeTooltip": "Grid ögelerinin boyutunu değiştirmeyi sağlar", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Brezilya Portekizcesi", + "AboutRyujinxContributorsButtonHeader": "Tüm katkıda bulunanları gör", + "SettingsTabSystemAudioVolume": "Ses Seviyesi: ", + "AudioVolumeTooltip": "Ses seviyesini değiştirir", + "SettingsTabSystemEnableInternetAccess": "Guest Internet Erişimi/LAN Modu", + "EnableInternetAccessTooltip": "Emüle edilen uygulamanın internete bağlanmasını sağlar.\n\nLAN modu bulunan oyunlar bu seçenek ile birbirine bağlanabilir ve sistemler aynı access point'e bağlanır. Bu gerçek konsolları da kapsar.\n\nNintendo sunucularına bağlanmayı sağlaMAZ. Internete bağlanmaya çalışan baz oyunların çökmesine sebep olabilr.\n\nEmin değilseniz devre dışı bırakın.", + "GameListContextMenuManageCheatToolTip": "Hileleri yönetmeyi sağlar", + "GameListContextMenuManageCheat": "Hileleri Yönet", + "ControllerSettingsStickRange": "Menzil:", + "DialogStopEmulationTitle": "Ryujinx - Emülasyonu Durdur", + "DialogStopEmulationMessage": "Emülasyonu durdurmak istediğinizden emin misiniz?", + "SettingsTabCpu": "İşlemci", + "SettingsTabAudio": "Ses", + "SettingsTabNetwork": "Ağ", + "SettingsTabNetworkConnection": "Ağ Bağlantısı", + "SettingsTabCpuCache": "İşlemci Belleği", + "SettingsTabCpuMemory": "CPU Hafızası", + "DialogUpdaterFlatpakNotSupportedMessage": "Lütfen Ryujinx'i FlatHub aracılığıyla güncelleyin.", + "UpdaterDisabledWarningTitle": "Güncelleyici Devre Dışı!", + "GameListContextMenuOpenSdModsDirectory": "Atmosphere Mod Dizini", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Uygulama Modlarını içeren alternatif SD kart Atmosfer dizinini açar. Gerçek donanım için paketlenmiş modlar için kullanışlıdır.", + "ControllerSettingsRotate90": "Saat yönünde 90° Döndür", + "IconSize": "Ikon Boyutu", + "IconSizeTooltip": "Oyun ikonlarının boyutunu değiştirmeyi sağlar", + "MenuBarOptionsShowConsole": "Konsol'u Göster", + "ShaderCachePurgeError": "Belirtilen shader cache temizlenirken hata {0}: {1}", + "UserErrorNoKeys": "Keys bulunamadı", + "UserErrorNoFirmware": "Firmware bulunamadı", + "UserErrorFirmwareParsingFailed": "Firmware çözümleme hatası", + "UserErrorApplicationNotFound": "Uygulama bulunamadı", + "UserErrorUnknown": "Bilinmeyen hata", + "UserErrorUndefined": "Tanımlanmayan hata", + "UserErrorNoKeysDescription": "Ryujinx 'prod.keys' dosyasını bulamadı", + "UserErrorNoFirmwareDescription": "Ryujinx yüklü herhangi firmware bulamadı", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx temin edilen firmware'i çözümleyemedi. Bu durum genellikle güncel olmayan keys'den kaynaklanır.", + "UserErrorApplicationNotFoundDescription": "Ryujinx belirtilen yolda geçerli bir uygulama bulamadı.", + "UserErrorUnknownDescription": "Bilinmeyen bir hata oluştu!", + "UserErrorUndefinedDescription": "Tanımlanmayan bir hata oluştu! Bu durum ile karşılaşılmamalıydı, lütfen bir geliştirici ile iletişime geçin!", + "OpenSetupGuideMessage": "Kurulum Kılavuzunu Aç", + "NoUpdate": "Güncelleme Yok", + "TitleUpdateVersionLabel": "Sürüm {0} - {1}", + "RyujinxInfo": "Ryujinx - Bilgi", + "RyujinxConfirm": "Ryujinx - Doğrulama", + "FileDialogAllTypes": "Tüm türler", + "Never": "Hiçbir Zaman", + "SwkbdMinCharacters": "En az {0} karakter uzunluğunda olmalı", + "SwkbdMinRangeCharacters": "{0}-{1} karakter uzunluğunda olmalı", + "SoftwareKeyboard": "Yazılım Klavyesi", + "DialogControllerAppletMessagePlayerRange": "Uygulama belirtilen türde {0} oyuncu istiyor:\n\nTÜRLER: {1}\n\nOYUNCULAR: {2}\n\n{3}Lütfen şimdi seçeneklerden giriş aygıtlarını ayarlayın veya Kapat'a basın.", + "DialogControllerAppletMessage": "Uygulama belirtilen türde tam olarak {0} oyuncu istiyor:\n\nTÜRLER: {1}\n\nOYUNCULAR: {2}\n\n{3}Lütfen şimdi seçeneklerden giriş aygıtlarını ayarlayın veya Kapat'a basın.", + "DialogControllerAppletDockModeSet": "Docked mode etkin. Handheld geçersiz.\n\n", + "UpdaterRenaming": "Eski dosyalar yeniden adlandırılıyor...", + "UpdaterRenameFailed": "Güncelleyici belirtilen dosyayı yeniden adlandıramadı: {0}", + "UpdaterAddingFiles": "Yeni Dosyalar Ekleniyor...", + "UpdaterExtracting": "Güncelleme Ayrıştırılıyor...", + "UpdaterDownloading": "Güncelleme İndiriliyor...", + "Game": "Oyun", + "Docked": "Yerleştirildi", + "Handheld": "El tipi", + "ConnectionError": "Bağlantı Hatası.", + "AboutPageDeveloperListMore": "{0} ve daha fazla...", + "ApiError": "API Hatası.", + "LoadingHeading": "{0} Yükleniyor", + "CompilingPPTC": "PTC Derleniyor", + "CompilingShaders": "Shaderlar Derleniyor", + "AllKeyboards": "Tüm Klavyeler", + "OpenFileDialogTitle": "Açmak için desteklenen bir dosya seçin", + "OpenFolderDialogTitle": "Ayrıştırılmamış oyun içeren bir klasör seçin", + "AllSupportedFormats": "Tüm Desteklenen Formatlar", + "RyujinxUpdater": "Ryujinx Güncelleyicisi", + "SettingsTabHotkeys": "Klavye Kısayolları", + "SettingsTabHotkeysHotkeys": "Klavye Kısayolları", + "SettingsTabHotkeysToggleVsyncHotkey": "VSync'i Etkinleştir/Devre Dışı Bırak:", + "SettingsTabHotkeysScreenshotHotkey": "Ekran Görüntüsü Al:", + "SettingsTabHotkeysShowUiHotkey": "Arayüzü Göster:", + "SettingsTabHotkeysPauseHotkey": "Durdur:", + "SettingsTabHotkeysToggleMuteHotkey": "Sustur:", + "ControllerMotionTitle": "Hareket Kontrol Seçenekleri", + "ControllerRumbleTitle": "Titreşim Seçenekleri", + "SettingsSelectThemeFileDialogTitle": "Tema Dosyası Seç", + "SettingsXamlThemeFile": "Xaml Tema Dosyası", + "AvatarWindowTitle": "Hesapları Yönet - Avatar", + "Amiibo": "Amiibo", + "Unknown": "Bilinmeyen", + "Usage": "Kullanım", + "Writable": "Yazılabilir", + "SelectDlcDialogTitle": "DLC dosyalarını seç", + "SelectUpdateDialogTitle": "Güncelleme dosyalarını seç", + "UserProfileWindowTitle": "Kullanıcı Profillerini Yönet", + "CheatWindowTitle": "Oyun Hilelerini Yönet", + "DlcWindowTitle": "Oyun DLC'lerini Yönet", + "UpdateWindowTitle": "Oyun Güncellemelerini Yönet", + "CheatWindowHeading": "{0} için Hile mevcut [{1}]", + "DlcWindowHeading": "{0} için DLC mevcut [{1}]", + "UserProfilesEditProfile": "Seçiliyi Düzenle", + "Cancel": "İptal", + "Save": "Kaydet", + "Discard": "Iskarta", + "UserProfilesSetProfileImage": "Profil Resmi Ayarla", + "UserProfileEmptyNameError": "İsim gerekli", + "UserProfileNoImageError": "Profil resmi ayarlanmalıdır", + "GameUpdateWindowHeading": "{0} için güncellemeler mevcut [{1}]", + "SettingsTabHotkeysResScaleUpHotkey": "Çözünürlüğü artır:", + "SettingsTabHotkeysResScaleDownHotkey": "Çözünürlüğü azalt:", + "UserProfilesName": "İsim:", + "UserProfilesUserId": "Kullanıcı Adı:", + "SettingsTabGraphicsBackend": "Grafik Arka Ucu", + "SettingsTabGraphicsBackendTooltip": "Kullanılacak Grafik Arka Uç", + "SettingsEnableTextureRecompression": "Yeniden Doku Sıkıştırılmasını Aktif Et", + "SettingsEnableTextureRecompressionTooltip": "4GB VRAM'in Altında Sistemler için önerilir.\n\nEmin değilseniz kapalı bırakın", + "SettingsTabGraphicsPreferredGpu": "Kullanılan GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Vulkan Grafik Arka Ucu ile kullanılacak Ekran Kartını Seçin.\n\nOpenGL'nin kullanacağı GPU'yu etkilemez.\n\n Emin değilseniz \"dGPU\" olarak işaretlenmiş GPU'ya ayarlayın. Eğer yoksa, dokunmadan bırakın.\n", + "SettingsAppRequiredRestartMessage": "Ryujinx'i Yeniden Başlatma Gerekli", + "SettingsGpuBackendRestartMessage": "Grafik Motoru ya da GPU ayarları değiştirildi. Bu işlemin uygulanması için yeniden başlatma gerekli.", + "SettingsGpuBackendRestartSubMessage": "Şimdi yeniden başlatmak istiyor musunuz?", + "RyujinxUpdaterMessage": "Ryujinx'i en son sürüme güncellemek ister misiniz?", + "SettingsTabHotkeysVolumeUpHotkey": "Sesi Arttır:", + "SettingsTabHotkeysVolumeDownHotkey": "Sesi Azalt:", + "SettingsEnableMacroHLE": "Macro HLE'yi Aktifleştir", + "SettingsEnableMacroHLETooltip": "GPU Macro kodunun yüksek seviye emülasyonu.\n\nPerformansı arttırır, ama bazı oyunlarda grafik hatalarına yol açabilir.\n\nEmin değilseniz AÇIK bırakın.", + "VolumeShort": "Ses", + "UserProfilesManageSaves": "Kayıtları Yönet", + "DeleteUserSave": "Bu oyun için kullanıcı kaydını silmek istiyor musunuz?", + "IrreversibleActionNote": "Bu eylem geri alınamaz.", + "SaveManagerHeading": "{0} için Kayıt Dosyalarını Yönet", + "SaveManagerTitle": "Kayıt Yöneticisi", + "Name": "İsim", + "Size": "Boyut", + "Search": "Ara", + "UserProfilesRecoverLostAccounts": "Kayıp Hesapları Kurtar", + "Recover": "Kurtar", + "UserProfilesRecoverHeading": "Aşağıdaki hesaplar için kayıtlar bulundu" +} diff --git a/src/Ryujinx.Ava/Assets/Locales/uk_UA.json b/src/Ryujinx.Ava/Assets/Locales/uk_UA.json new file mode 100644 index 00000000..111337a4 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/uk_UA.json @@ -0,0 +1,614 @@ +{ + "Language": "Yкраїнська", + "MenuBarFileOpenApplet": "Відкрити аплет", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "Відкрийте аплет Mii Editor в автономному режимі", + "SettingsTabInputDirectMouseAccess": "Прямий доступ мишею", + "SettingsTabSystemMemoryManagerMode": "Режим диспетчера пам'яті:", + "SettingsTabSystemMemoryManagerModeSoftware": "Програмне забезпечення", + "SettingsTabSystemMemoryManagerModeHost": "Хост (швидко)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Неперевірений хост (найшвидший, небезпечний)", + "MenuBarFile": "_Файл", + "MenuBarFileOpenFromFile": "_Завантажити програму з файлу", + "MenuBarFileOpenUnpacked": "Завантажити _розпаковану гру", + "MenuBarFileOpenEmuFolder": "Відкрити теку Ryujinx", + "MenuBarFileOpenLogsFolder": "Відкрити теку журналів змін", + "MenuBarFileExit": "_Вихід", + "MenuBarOptions": "Опції", + "MenuBarOptionsToggleFullscreen": "Перемкнути на весь екран", + "MenuBarOptionsStartGamesInFullscreen": "Запускати ігри на весь екран", + "MenuBarOptionsStopEmulation": "Зупинити емуляцію", + "MenuBarOptionsSettings": "_Налаштування", + "MenuBarOptionsManageUserProfiles": "_Керування профілями користувачів", + "MenuBarActions": "_Дії", + "MenuBarOptionsSimulateWakeUpMessage": "Симулювати повідомлення про пробудження", + "MenuBarActionsScanAmiibo": "Сканувати Amiibo", + "MenuBarTools": "_Інструменти", + "MenuBarToolsInstallFirmware": "Встановити прошивку", + "MenuBarFileToolsInstallFirmwareFromFile": "Встановити прошивку з XCI або ZIP", + "MenuBarFileToolsInstallFirmwareFromDirectory": "Встановити прошивку з теки", + "MenuBarHelp": "Довідка", + "MenuBarHelpCheckForUpdates": "Перевірити оновлення", + "MenuBarHelpAbout": "Про програму", + "MenuSearch": "Пошук...", + "GameListHeaderFavorite": "Вибране", + "GameListHeaderIcon": "Значок", + "GameListHeaderApplication": "Назва", + "GameListHeaderDeveloper": "Розробник", + "GameListHeaderVersion": "Версія", + "GameListHeaderTimePlayed": "Зіграно часу", + "GameListHeaderLastPlayed": "Остання гра", + "GameListHeaderFileExtension": "Розширення файлу", + "GameListHeaderFileSize": "Розмір файлу", + "GameListHeaderPath": "Шлях", + "GameListContextMenuOpenUserSaveDirectory": "Відкрити каталог збереження користувача", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "Відкриває каталог, який містить збереження користувача програми", + "GameListContextMenuOpenDeviceSaveDirectory": "Відкрити каталог пристроїв користувача", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "Відкриває каталог, який містить збереження пристрою програми", + "GameListContextMenuOpenBcatSaveDirectory": "Відкрити каталог користувача BCAT", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "Відкриває каталог, який містить BCAT-збереження програми", + "GameListContextMenuManageTitleUpdates": "Керування оновленнями заголовків", + "GameListContextMenuManageTitleUpdatesToolTip": "Відкриває вікно керування оновленням заголовка", + "GameListContextMenuManageDlc": "Керування DLC", + "GameListContextMenuManageDlcToolTip": "Відкриває вікно керування DLC", + "GameListContextMenuOpenModsDirectory": "Відкрити каталог модифікацій", + "GameListContextMenuOpenModsDirectoryToolTip": "Відкриває каталог, який містить модифікації програм", + "GameListContextMenuCacheManagement": "Керування кешем", + "GameListContextMenuCacheManagementPurgePptc": "Очистити кеш PPTC", + "GameListContextMenuCacheManagementPurgePptcToolTip": "Видаляє кеш PPTC програми", + "GameListContextMenuCacheManagementPurgeShaderCache": "Очистити кеш шейдерів", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "Видаляє кеш шейдерів програми", + "GameListContextMenuCacheManagementOpenPptcDirectory": "Відкрити каталог PPTC", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "Відкриває каталог, який містить кеш PPTC програми", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "Відкрити каталог кешу шейдерів", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "Відкриває каталог, який містить кеш шейдерів програми", + "GameListContextMenuExtractData": "Видобути дані", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "Видобуває розділ ExeFS із поточної конфігурації програми (включаючи оновлення)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "Видобуває розділ RomFS із поточної конфігурації програми (включаючи оновлення)", + "GameListContextMenuExtractDataLogo": "Логотип", + "GameListContextMenuExtractDataLogoToolTip": "Видобуває розділ логотипу з поточної конфігурації програми (включаючи оновлення)", + "StatusBarGamesLoaded": "{0}/{1} Ігор завантажено", + "StatusBarSystemVersion": "Версія системи: {0}", + "Settings": "Налаштування", + "SettingsTabGeneral": "Інтерфейс користувача", + "SettingsTabGeneralGeneral": "Загальні", + "SettingsTabGeneralEnableDiscordRichPresence": "Увімкнути розширену присутність Discord", + "SettingsTabGeneralCheckUpdatesOnLaunch": "Перевіряти наявність оновлень під час запуску", + "SettingsTabGeneralShowConfirmExitDialog": "Показати діалогове вікно «Підтвердити вихід».", + "SettingsTabGeneralHideCursorOnIdle": "Приховати курсор у режимі очікування", + "SettingsTabGeneralGameDirectories": "Каталоги ігор", + "SettingsTabGeneralAdd": "Додати", + "SettingsTabGeneralRemove": "Видалити", + "SettingsTabSystem": "Система", + "SettingsTabSystemCore": "Ядро", + "SettingsTabSystemSystemRegion": "Регіон системи:", + "SettingsTabSystemSystemRegionJapan": "Японія", + "SettingsTabSystemSystemRegionUSA": "США", + "SettingsTabSystemSystemRegionEurope": "Європа", + "SettingsTabSystemSystemRegionAustralia": "Австралія", + "SettingsTabSystemSystemRegionChina": "Китай", + "SettingsTabSystemSystemRegionKorea": "Корея", + "SettingsTabSystemSystemRegionTaiwan": "Тайвань", + "SettingsTabSystemSystemLanguage": "Мова системи:", + "SettingsTabSystemSystemLanguageJapanese": "Японська", + "SettingsTabSystemSystemLanguageAmericanEnglish": "Англійська (США)", + "SettingsTabSystemSystemLanguageFrench": "Французька", + "SettingsTabSystemSystemLanguageGerman": "Німецька", + "SettingsTabSystemSystemLanguageItalian": "Італійська", + "SettingsTabSystemSystemLanguageSpanish": "Іспанська", + "SettingsTabSystemSystemLanguageChinese": "Китайська", + "SettingsTabSystemSystemLanguageKorean": "Корейська", + "SettingsTabSystemSystemLanguageDutch": "Нідерландська", + "SettingsTabSystemSystemLanguagePortuguese": "Португальська", + "SettingsTabSystemSystemLanguageRussian": "Російська", + "SettingsTabSystemSystemLanguageTaiwanese": "Тайванська", + "SettingsTabSystemSystemLanguageBritishEnglish": "Англійська (Великобританія)", + "SettingsTabSystemSystemLanguageCanadianFrench": "Французька (Канада)", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "Іспанська (Латиноамериканська)", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "Спрощена китайська", + "SettingsTabSystemSystemLanguageTraditionalChinese": "Традиційна китайська", + "SettingsTabSystemSystemTimeZone": "Часовий пояс системи:", + "SettingsTabSystemSystemTime": "Час системи:", + "SettingsTabSystemEnableVsync": "Вертикальна синхронізація", + "SettingsTabSystemEnablePptc": "PPTC (профільований постійний кеш перекладу)", + "SettingsTabSystemEnableFsIntegrityChecks": "Перевірка цілісності FS", + "SettingsTabSystemAudioBackend": "Аудіосистема:", + "SettingsTabSystemAudioBackendDummy": "Dummy", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "Хитрощі", + "SettingsTabSystemHacksNote": " (може викликати нестабільність)", + "SettingsTabSystemExpandDramSize": "Використовувати альтернативне розташування пам'яті (розробники)", + "SettingsTabSystemIgnoreMissingServices": "Ігнорувати відсутні служби", + "SettingsTabGraphics": "Графіка", + "SettingsTabGraphicsAPI": "Графічний API", + "SettingsTabGraphicsEnableShaderCache": "Увімкнути кеш шейдерів", + "SettingsTabGraphicsAnisotropicFiltering": "Анізотропна фільтрація:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "Авто", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "Роздільна здатність:", + "SettingsTabGraphicsResolutionScaleCustom": "Користувацька (не рекомендовано)", + "SettingsTabGraphicsResolutionScaleNative": "Стандартний (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "Співвідношення сторін:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "Розтягнути до розміру вікна", + "SettingsTabGraphicsDeveloperOptions": "Налаштування виробника", + "SettingsTabGraphicsShaderDumpPath": "Шлях скидання графічного шейдера:", + "SettingsTabLogging": "Налагодження", + "SettingsTabLoggingLogging": "Налагодження", + "SettingsTabLoggingEnableLoggingToFile": "Увімкнути налагодження у файл", + "SettingsTabLoggingEnableStubLogs": "Увімкнути журнали заглушки", + "SettingsTabLoggingEnableInfoLogs": "Увімкнути інформаційні журнали", + "SettingsTabLoggingEnableWarningLogs": "Увімкнути журнали попереджень", + "SettingsTabLoggingEnableErrorLogs": "Увімкнути журнали помилок", + "SettingsTabLoggingEnableTraceLogs": "Увімкнути журнали трасування", + "SettingsTabLoggingEnableGuestLogs": "Увімкнути журнали гостей", + "SettingsTabLoggingEnableFsAccessLogs": "Увімкнути журнали доступу Fs", + "SettingsTabLoggingFsGlobalAccessLogMode": "Режим журналу глобального доступу Fs:", + "SettingsTabLoggingDeveloperOptions": "Параметри розробника (УВАГА: знизиться продуктивність)", + "SettingsTabLoggingGraphicsBackendLogLevel": "Рівень журналу графічного сервера:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "Ні", + "SettingsTabLoggingGraphicsBackendLogLevelError": "Помилка", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "Уповільнення", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "Все", + "SettingsTabLoggingEnableDebugLogs": "Увімкнути журнали налагодження", + "SettingsTabInput": "Введення", + "SettingsTabInputEnableDockedMode": "Режим док-станції", + "SettingsTabInputDirectKeyboardAccess": "Прямий доступ з клавіатури", + "SettingsButtonSave": "Зберегти", + "SettingsButtonClose": "Закрити", + "SettingsButtonOk": "Гаразд", + "SettingsButtonCancel": "Скасувати", + "SettingsButtonApply": "Застосувати", + "ControllerSettingsPlayer": "Гравець", + "ControllerSettingsPlayer1": "Гравець 1", + "ControllerSettingsPlayer2": "Гравець 2", + "ControllerSettingsPlayer3": "Гравець 3", + "ControllerSettingsPlayer4": "Гравець 4", + "ControllerSettingsPlayer5": "Гравець 5", + "ControllerSettingsPlayer6": "Гравець 6", + "ControllerSettingsPlayer7": "Гравець 7", + "ControllerSettingsPlayer8": "Гравець 8", + "ControllerSettingsHandheld": "Портативний", + "ControllerSettingsInputDevice": "Пристрій введення", + "ControllerSettingsRefresh": "Оновити", + "ControllerSettingsDeviceDisabled": "Вимкнено", + "ControllerSettingsControllerType": "Тип контролера", + "ControllerSettingsControllerTypeHandheld": "Портативний", + "ControllerSettingsControllerTypeProController": "Контролер Pro", + "ControllerSettingsControllerTypeJoyConPair": "Обидва JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "Лівий JoyCon", + "ControllerSettingsControllerTypeJoyConRight": "Правий JoyCon", + "ControllerSettingsProfile": "Профіль", + "ControllerSettingsProfileDefault": "Типовий", + "ControllerSettingsLoad": "Завантажити", + "ControllerSettingsAdd": "Додати", + "ControllerSettingsRemove": "Видалити", + "ControllerSettingsButtons": "Кнопки", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "Панель направлення", + "ControllerSettingsDPadUp": "Вгору", + "ControllerSettingsDPadDown": "Вниз", + "ControllerSettingsDPadLeft": "Вліво", + "ControllerSettingsDPadRight": "Вправо", + "ControllerSettingsLStick": "Лівий джойстик", + "ControllerSettingsLStickButton": "Кнопка", + "ControllerSettingsLStickUp": "Вгору", + "ControllerSettingsLStickDown": "Вниз", + "ControllerSettingsLStickLeft": "Вліво", + "ControllerSettingsLStickRight": "Вправо", + "ControllerSettingsLStickStick": "Джойстик", + "ControllerSettingsLStickInvertXAxis": "Інвертувати джойстик по X", + "ControllerSettingsLStickInvertYAxis": "Інвертувати джойстик по Y", + "ControllerSettingsLStickDeadzone": "Мертва зона:", + "ControllerSettingsRStick": "Правий джойстик", + "ControllerSettingsRStickButton": "Кнопка", + "ControllerSettingsRStickUp": "Вгору", + "ControllerSettingsRStickDown": "Вниз", + "ControllerSettingsRStickLeft": "Вліво", + "ControllerSettingsRStickRight": "Вправо", + "ControllerSettingsRStickStick": "Джойстик", + "ControllerSettingsRStickInvertXAxis": "Інвертувати джойстик по X", + "ControllerSettingsRStickInvertYAxis": "Інвертувати джойстик по Y", + "ControllerSettingsRStickDeadzone": "Мертва зона:", + "ControllerSettingsTriggersLeft": "Тригери ліворуч", + "ControllerSettingsTriggersRight": "Тригери праворуч", + "ControllerSettingsTriggersButtonsLeft": "Кнопки тригерів ліворуч", + "ControllerSettingsTriggersButtonsRight": "Кнопки тригерів праворуч", + "ControllerSettingsTriggers": "Тригери", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "Кнопки ліворуч", + "ControllerSettingsExtraButtonsRight": "Кнопки праворуч", + "ControllerSettingsMisc": "Різне", + "ControllerSettingsTriggerThreshold": "Поріг спрацьовування:", + "ControllerSettingsMotion": "Рух", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "Використовувати рух, сумісний з CemuHook", + "ControllerSettingsMotionControllerSlot": "Слот контролера:", + "ControllerSettingsMotionMirrorInput": "Дзеркальний вхід", + "ControllerSettingsMotionRightJoyConSlot": "Правий слот JoyCon:", + "ControllerSettingsMotionServerHost": "Хост сервера:", + "ControllerSettingsMotionGyroSensitivity": "Чутливість гіроскопа:", + "ControllerSettingsMotionGyroDeadzone": "Мертва зона гіроскопа:", + "ControllerSettingsSave": "Зберегти", + "ControllerSettingsClose": "Закрити", + "UserProfilesSelectedUserProfile": "Вибраний профіль користувача:", + "UserProfilesSaveProfileName": "Зберегти ім'я профілю", + "UserProfilesChangeProfileImage": "Змінити зображення профілю", + "UserProfilesAvailableUserProfiles": "Доступні профілі користувачів:", + "UserProfilesAddNewProfile": "Створити профіль", + "UserProfilesDeleteSelectedProfile": "Видалити вибране", + "UserProfilesClose": "Закрити", + "ProfileImageSelectionTitle": "Вибір зображення профілю", + "ProfileImageSelectionHeader": "Виберіть зображення профілю", + "ProfileImageSelectionNote": "Ви можете імпортувати власне зображення профілю або вибрати аватар із мікропрограми системи", + "ProfileImageSelectionImportImage": "Імпорт файлу зображення", + "ProfileImageSelectionSelectAvatar": "Виберіть аватар прошивки ", + "InputDialogTitle": "Діалог введення", + "InputDialogOk": "Гаразд", + "InputDialogCancel": "Скасувати", + "InputDialogAddNewProfileTitle": "Виберіть ім'я профілю", + "InputDialogAddNewProfileHeader": "Будь ласка, введіть ім'я профілю", + "InputDialogAddNewProfileSubtext": "(Макс. довжина: {0})", + "AvatarChoose": "Вибрати", + "AvatarSetBackgroundColor": "Встановити колір фону", + "AvatarClose": "Закрити", + "ControllerSettingsLoadProfileToolTip": "Завантажити профіль", + "ControllerSettingsAddProfileToolTip": "Додати профіль", + "ControllerSettingsRemoveProfileToolTip": "Видалити профіль", + "ControllerSettingsSaveProfileToolTip": "Зберегти профіль", + "MenuBarFileToolsTakeScreenshot": "Зробити знімок екрана", + "MenuBarFileToolsHideUi": "Сховати інтерфейс", + "GameListContextMenuToggleFavorite": "Перемкнути вибране", + "GameListContextMenuToggleFavoriteToolTip": "Перемкнути улюблений статус гри", + "SettingsTabGeneralTheme": "Тема", + "SettingsTabGeneralThemeCustomTheme": "Користувацький шлях до теми", + "SettingsTabGeneralThemeBaseStyle": "Базовий стиль", + "SettingsTabGeneralThemeBaseStyleDark": "Темна", + "SettingsTabGeneralThemeBaseStyleLight": "Світла", + "SettingsTabGeneralThemeEnableCustomTheme": "Увімкнути користуваьку тему", + "ButtonBrowse": "Огляд", + "ControllerSettingsConfigureGeneral": "Налаштування", + "ControllerSettingsRumble": "Вібрація", + "ControllerSettingsRumbleStrongMultiplier": "Множник сильної вібрації", + "ControllerSettingsRumbleWeakMultiplier": "Множник слабкої вібрації", + "DialogMessageSaveNotAvailableMessage": "Немає збережених даних для {0} [{1:x16}]", + "DialogMessageSaveNotAvailableCreateSaveMessage": "Хочете створити дані збереження для цієї гри?", + "DialogConfirmationTitle": "Ryujinx - Підтвердження", + "DialogUpdaterTitle": "Ryujinx - Програма оновлення", + "DialogErrorTitle": "Ryujinx - Помилка", + "DialogWarningTitle": "Ryujinx - Попередження", + "DialogExitTitle": "Ryujinx - Вихід", + "DialogErrorMessage": "У Ryujinx сталася помилка", + "DialogExitMessage": "Ви впевнені, що бажаєте закрити Ryujinx?", + "DialogExitSubMessage": "Усі незбережені дані буде втрачено!", + "DialogMessageCreateSaveErrorMessage": "Під час створення вказаних даних збереження сталася помилка: {0}", + "DialogMessageFindSaveErrorMessage": "Під час пошуку вказаних даних збереження сталася помилка: {0}", + "FolderDialogExtractTitle": "Виберіть папку для видобування", + "DialogNcaExtractionMessage": "Видобування розділу {0} з {1}...", + "DialogNcaExtractionTitle": "Ryujinx - Екстрактор розділів NCA", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Помилка видобування. Основний NCA не був присутній у вибраному файлі.", + "DialogNcaExtractionCheckLogErrorMessage": "Помилка видобування. Прочитайте файл журналу для отримання додаткової інформації.", + "DialogNcaExtractionSuccessMessage": "Видобування успішно завершено.", + "DialogUpdaterConvertFailedMessage": "Не вдалося конвертувати поточну версію Ryujinx.", + "DialogUpdaterCancelUpdateMessage": "Скасування оновлення!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "Ви вже використовуєте останню версію Ryujinx!", + "DialogUpdaterFailedToGetVersionMessage": "Під час спроби отримати інформацію про випуск із GitHub Release сталася помилка. Це може бути спричинено, якщо новий випуск компілюється GitHub Actions. Повторіть спробу через кілька хвилин.", + "DialogUpdaterConvertFailedGithubMessage": "Не вдалося конвертувати отриману версію Ryujinx із випуску Github.", + "DialogUpdaterDownloadingMessage": "Завантаження оновлення...", + "DialogUpdaterExtractionMessage": "Видобування оновлення...", + "DialogUpdaterRenamingMessage": "Перейменування оновлення...", + "DialogUpdaterAddingFilesMessage": "Додавання нового оновлення...", + "DialogUpdaterCompleteMessage": "Оновлення завершено!", + "DialogUpdaterRestartMessage": "Перезапустити Ryujinx зараз?", + "DialogUpdaterArchNotSupportedMessage": "Ви використовуєте не підтримувану архітектуру системи!", + "DialogUpdaterArchNotSupportedSubMessage": "(Підтримуються лише системи x64!)", + "DialogUpdaterNoInternetMessage": "Ви не підключені до Інтернету!", + "DialogUpdaterNoInternetSubMessage": "Будь ласка, переконайтеся, що у вас є робоче підключення до Інтернету!", + "DialogUpdaterDirtyBuildMessage": "Ви не можете оновити брудну збірку Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "Будь ласка, завантажте Ryujinx на https://ryujinx.org/, якщо ви шукаєте підтримувану версію.", + "DialogRestartRequiredMessage": "Потрібен перезапуск", + "DialogThemeRestartMessage": "Тему збережено. Щоб застосувати тему, потрібен перезапуск.", + "DialogThemeRestartSubMessage": "Ви хочете перезапустити", + "DialogFirmwareInstallEmbeddedMessage": "Бажаєте встановити прошивку, вбудовану в цю гру? (Прошивка {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "Встановлену прошивку не знайдено, але Ryujinx вдалося встановити прошивку {0} з наданої гри.\\nТепер запуститься емулятор.", + "DialogFirmwareNoFirmwareInstalledMessage": "Прошивка не встановлена", + "DialogFirmwareInstalledMessage": "Встановлено прошивку {0}", + "DialogOpenSettingsWindowLabel": "Відкрити вікно налаштувань", + "DialogControllerAppletTitle": "Аплет контролера", + "DialogMessageDialogErrorExceptionMessage": "Помилка показу діалогового вікна повідомлення: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "Помилка показу програмної клавіатури: {0}", + "DialogErrorAppletErrorExceptionMessage": "Помилка показу діалогового вікна ErrorApplet: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\nДля отримання додаткової інформації про те, як виправити цю помилку, дотримуйтесь нашого посібника з налаштування.", + "DialogUserErrorDialogTitle": "Помилка Ryujinx ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "Під час отримання інформації з API сталася помилка.", + "DialogAmiiboApiConnectErrorMessage": "Неможливо підключитися до сервера Amiibo API. Можливо, служба не працює або вам потрібно перевірити, чи є підключення до Інтернету.", + "DialogProfileInvalidProfileErrorMessage": "Профіль {0} несумісний із поточною системою конфігурації вводу.", + "DialogProfileDefaultProfileOverwriteErrorMessage": "Стандартний профіль не можна перезаписати", + "DialogProfileDeleteProfileTitle": "Видалення профілю", + "DialogProfileDeleteProfileMessage": "Цю дію неможливо скасувати. Ви впевнені, що бажаєте продовжити?", + "DialogWarning": "Увага", + "DialogPPTCDeletionMessage": "Ви збираєтеся видалити кеш PPTC для:\n\n{0}\n\nВи впевнені, що бажаєте продовжити?", + "DialogPPTCDeletionErrorMessage": "Помилка очищення кешу PPTC на {0}: {1}", + "DialogShaderDeletionMessage": "Ви збираєтеся видалити кеш шейдерів для:\n\n{0}\n\nВи впевнені, що бажаєте продовжити?", + "DialogShaderDeletionErrorMessage": "Помилка очищення кешу шейдерів на {0}: {1}", + "DialogRyujinxErrorMessage": "У Ryujinx сталася помилка", + "DialogInvalidTitleIdErrorMessage": "Помилка інтерфейсу: вибрана гра не мала дійсного ідентифікатора назви", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "Дійсна прошивка системи не знайдена в {0}.", + "DialogFirmwareInstallerFirmwareInstallTitle": "Встановити прошивку {0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "Буде встановлено версію системи {0}.", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\nЦе замінить поточну версію системи {0}.", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\nВи хочете продовжити?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "Встановлення прошивки...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "Версію системи {0} успішно встановлено.", + "DialogUserProfileDeletionWarningMessage": "Якщо вибраний профіль буде видалено, інші профілі не відкриватимуться", + "DialogUserProfileDeletionConfirmMessage": "Ви хочете видалити вибраний профіль", + "DialogControllerSettingsModifiedConfirmMessage": "Поточні налаштування контролера оновлено.", + "DialogControllerSettingsModifiedConfirmSubMessage": "Ви хочете зберегти?", + "DialogLoadNcaErrorMessage": "{0}. Файл з помилкою: {1}", + "DialogDlcNoDlcErrorMessage": "Зазначений файл не містить DLC для вибраного заголовку!", + "DialogPerformanceCheckLoggingEnabledMessage": "Ви увімкнули журнал налагодження, призначений лише для розробників.", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "Для оптимальної продуктивності рекомендується вимкнути ведення журналу налагодження. Ви хочете вимкнути ведення журналу налагодження зараз?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "Ви увімкнули скидання шейдерів, призначений лише для розробників.", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "Для оптимальної продуктивності рекомендується вимкнути скидання шейдерів. Ви хочете вимкнути скидання шейдерів зараз?", + "DialogLoadAppGameAlreadyLoadedMessage": "Гру вже завантажено", + "DialogLoadAppGameAlreadyLoadedSubMessage": "Зупиніть емуляцію або закрийте емулятор перед запуском іншої гри.", + "DialogUpdateAddUpdateErrorMessage": "Зазначений файл не містить оновлення для вибраного заголовка!", + "DialogSettingsBackendThreadingWarningTitle": "Попередження - потокове керування сервером", + "DialogSettingsBackendThreadingWarningMessage": "Ryujinx потрібно перезапустити після зміни цього параметра, щоб він застосовувався повністю. Залежно від вашої платформи вам може знадобитися вручну вимкнути власну багатопотоковість драйвера під час використання Ryujinx.", + "SettingsTabGraphicsFeaturesOptions": "Особливості", + "SettingsTabGraphicsBackendMultithreading": "Багатопотоковість графічного сервера:", + "CommonAuto": "Авто", + "CommonOff": "Вимкнути", + "CommonOn": "Увімкнути", + "InputDialogYes": "Так", + "InputDialogNo": "Ні", + "DialogProfileInvalidProfileNameErrorMessage": "Ім'я файлу містить неприпустимі символи. Будь ласка, спробуйте ще раз.", + "MenuBarOptionsPauseEmulation": "Пауза", + "MenuBarOptionsResumeEmulation": "Продовжити", + "AboutUrlTooltipMessage": "Натисніть, щоб відкрити сайт Ryujinx у браузері за замовчування.", + "AboutDisclaimerMessage": "Ryujinx жодним чином не пов’язано з Nintendo™,\nчи будь-яким із їхніх партнерів.", + "AboutAmiiboDisclaimerMessage": "AmiiboAPI (www.amiiboapi.com) використовується в нашій емуляції Amiibo.", + "AboutPatreonUrlTooltipMessage": "Натисніть, щоб відкрити сторінку Patreon Ryujinx у вашому браузері за замовчування.", + "AboutGithubUrlTooltipMessage": "Натисніть, щоб відкрити сторінку GitHub Ryujinx у браузері за замовчуванням.", + "AboutDiscordUrlTooltipMessage": "Натисніть, щоб відкрити запрошення на сервер Discord Ryujinx у браузері за замовчуванням.", + "AboutTwitterUrlTooltipMessage": "Натисніть, щоб відкрити сторінку Twitter Ryujinx у браузері за замовчуванням.", + "AboutRyujinxAboutTitle": "Про програму:", + "AboutRyujinxAboutContent": "Ryujinx — це емулятор для Nintendo Switch™.\nБудь ласка, підтримайте нас на Patreon.\nОтримуйте всі останні новини в нашому Twitter або Discord.\nРозробники, які хочуть зробити внесок, можуть дізнатися більше на нашому GitHub або в Discord.", + "AboutRyujinxMaintainersTitle": "Підтримується:", + "AboutRyujinxMaintainersContentTooltipMessage": "Натисніть, щоб відкрити сторінку співавторів у вашому браузері за замовчування.", + "AboutRyujinxSupprtersTitle": "Підтримується на Patreon:", + "AmiiboSeriesLabel": "Серія Amiibo", + "AmiiboCharacterLabel": "Персонаж", + "AmiiboScanButtonLabel": "Сканувати", + "AmiiboOptionsShowAllLabel": "Показати всі Amiibo", + "AmiiboOptionsUsRandomTagLabel": "Хитрість: Використовувати випадковий тег Uuid", + "DlcManagerTableHeadingEnabledLabel": "Увімкнено", + "DlcManagerTableHeadingTitleIdLabel": "ID заголовка", + "DlcManagerTableHeadingContainerPathLabel": "Шлях до контейнеру", + "DlcManagerTableHeadingFullPathLabel": "Повний шлях", + "DlcManagerRemoveAllButton": "Видалити все", + "DlcManagerEnableAllButton": "Увімкнути всі", + "DlcManagerDisableAllButton": "Вимкнути всі", + "MenuBarOptionsChangeLanguage": "Змінити мову", + "CommonSort": "Сортувати", + "CommonShowNames": "Показати назви", + "CommonFavorite": "Вибрані", + "OrderAscending": "За зростанням", + "OrderDescending": "За спаданням", + "SettingsTabGraphicsFeatures": "Функції та вдосконалення", + "ErrorWindowTitle": "Вікно помилок", + "ToggleDiscordTooltip": "Виберіть, чи відображати Ryujinx у вашій «поточній грі» в Discord", + "AddGameDirBoxTooltip": "Введіть каталог ігор, щоб додати до списку", + "AddGameDirTooltip": "Додати каталог гри до списку", + "RemoveGameDirTooltip": "Видалити вибраний каталог гри", + "CustomThemeCheckTooltip": "Використовуйте користувацьку тему Avalonia для графічного інтерфейсу, щоб змінити вигляд меню емулятора", + "CustomThemePathTooltip": "Шлях до користувацької теми графічного інтерфейсу", + "CustomThemeBrowseTooltip": "Огляд користувацької теми графічного інтерфейсу", + "DockModeToggleTooltip": "У режимі док-станції емульована система веде себе як приєднаний Nintendo Switch. Це покращує точність графіки в більшості ігор. І навпаки, вимкнення цього призведе до того, що емульована система поводитиметься як портативний комутатор Nintendo, погіршуючи якість графіки.\n\nНалаштуйте елементи керування для гравця 1, якщо плануєте використовувати режим док-станції; налаштуйте ручні елементи керування, якщо плануєте використовувати портативний режим.\n\nЗалиште увімкненим, якщо не впевнені.", + "DirectKeyboardTooltip": "Підтримка прямого доступу з клавіатури (HID). Надає іграм доступ до клавіатури як пристрою для введення тексту.", + "DirectMouseTooltip": "Підтримка прямого доступу миші (HID). Надає іграм доступ до миші як вказівного пристрою.", + "RegionTooltip": "Змінити регіон системи", + "LanguageTooltip": "Змінити мову системи", + "TimezoneTooltip": "Змінити часовий пояс системи", + "TimeTooltip": "Змінити час системи", + "VSyncToggleTooltip": "Емульована вертикальна синхронізація консолі. По суті, обмежувач кадрів для більшості ігор; його вимкнення може призвести до того, що ігри працюватимуть на вищій швидкості, екрани завантаження триватимуть довше чи зупинятимуться.\n\nМожна перемикати в грі гарячою клавішею за вашим бажанням. Ми рекомендуємо зробити це, якщо ви плануєте вимкнути його.\n\nЗалиште увімкненим, якщо не впевнені.", + "PptcToggleTooltip": "Зберігає перекладені функції JIT, щоб їх не потрібно було перекладати кожного разу, коли гра завантажується.\n\nЗменшує заїкання та значно прискорює час завантаження після першого завантаження гри.\n\nЗалиште увімкненим, якщо не впевнені.", + "FsIntegrityToggleTooltip": "Перевіряє наявність пошкоджених файлів під час завантаження гри, і якщо виявлено пошкоджені файли, показує помилку хешу в журналі.\n\nНе впливає на продуктивність і призначений для усунення несправностей.\n\nЗалиште увімкненим, якщо не впевнені.", + "AudioBackendTooltip": "Змінює серверну частину, яка використовується для відтворення аудіо.\n\nSDL2 є кращим, тоді як OpenAL і SoundIO використовуються як резервні варіанти. Dummy не матиме звуку.\n\nВстановіть SDL2, якщо не впевнені.", + "MemoryManagerTooltip": "Змінює спосіб відображення та доступу до гостьової пам’яті. Значно впливає на продуктивність емульованого ЦП.\n\nВстановіть «Неперевірений хост», якщо не впевнені.", + "MemoryManagerSoftwareTooltip": "Використовує програмну таблицю сторінок для перекладу адрес. Найвища точність, але найповільніша продуктивність.", + "MemoryManagerHostTooltip": "Пряме відображення пам'яті в адресному просторі хосту. Набагато швидша компіляція та виконання JIT.", + "MemoryManagerUnsafeTooltip": "Пряме відображення пам’яті, але не маскує адресу в гостьовому адресному просторі перед доступом. Швидше, але ціною безпеки. Гостьова програма може отримати доступ до пам’яті з будь-якого місця в Ryujinx, тому запускайте в цьому режимі лише програми, яким ви довіряєте.", + "DRamTooltip": "Використовує альтернативний макет MemoryMode для імітації моделі розробки Switch.\n\nЦе корисно лише для пакетів текстур з вищою роздільною здатністю або модифікацій із роздільною здатністю 4K. НЕ покращує продуктивність.\n\nЗалиште вимкненим, якщо не впевнені.", + "IgnoreMissingServicesTooltip": "Ігнорує нереалізовані служби Horizon OS. Це може допомогти в обході збоїв під час завантаження певних ігор.\n\nЗалиште вимкненим, якщо не впевнені.", + "GraphicsBackendThreadingTooltip": "Виконує команди графічного сервера в другому потоці.\n\nПрискорює компіляцію шейдерів, зменшує затримки та покращує продуктивність драйверів GPU без власної підтримки багатопоточності. Трохи краща продуктивність на драйверах з багатопотоковістю.\nВстановіть значення «Авто», якщо не впевнені", + "GalThreadingTooltip": "Виконує команди графічного сервера в другому потоці.\n\nПрискорює компіляцію шейдерів, зменшує затримки та покращує продуктивність драйверів GPU без власної підтримки багатопоточності. Трохи краща продуктивність на драйверах з багатопотоковістю.\n\nВстановіть значення «Авто», якщо не впевнені.", + "ShaderCacheToggleTooltip": "Зберігає кеш дискового шейдера, що зменшує затримки під час наступних запусків.\n\nЗалиште увімкненим, якщо не впевнені.", + "ResolutionScaleTooltip": "Масштаб роздільної здатності, застосована до відповідних цілей візуалізації", + "ResolutionScaleEntryTooltip": "Масштаб роздільної здатності з плаваючою комою, наприклад 1,5. Не інтегральні масштаби, швидше за все, спричинять проблеми або збій.", + "AnisotropyTooltip": "Рівень анізотропної фільтрації (встановіть на «Авто», щоб використовувати значення, яке вимагає гра)", + "AspectRatioTooltip": "Співвідношення сторін, застосоване до вікна візуалізації.", + "ShaderDumpPathTooltip": "Шлях скидання графічних шейдерів", + "FileLogTooltip": "Зберігає журнал консолі у файл журналу на диску. Не впливає на продуктивність.", + "StubLogTooltip": "Друкує повідомлення журналу-заглушки на консолі. Не впливає на продуктивність.", + "InfoLogTooltip": "Друкує повідомлення інформаційного журналу на консолі. Не впливає на продуктивність.", + "WarnLogTooltip": "Друкує повідомлення журналу попереджень у консолі. Не впливає на продуктивність.", + "ErrorLogTooltip": "Друкує повідомлення журналу помилок у консолі. Не впливає на продуктивність.", + "TraceLogTooltip": "Друкує повідомлення журналу трасування на консолі. Не впливає на продуктивність.", + "GuestLogTooltip": "Друкує повідомлення журналу гостей у консолі. Не впливає на продуктивність.", + "FileAccessLogTooltip": "Друкує повідомлення журналу доступу до файлів у консолі.", + "FSAccessLogModeTooltip": "Вмикає виведення журналу доступу до FS на консоль. Можливі режими 0-3", + "DeveloperOptionTooltip": "Використовуйте з обережністю", + "OpenGlLogLevel": "Потрібно увімкнути відповідні рівні журналу", + "DebugLogTooltip": "Друкує повідомлення журналу налагодження на консолі.\n\nВикористовуйте це лише за спеціальною вказівкою співробітника, оскільки це ускладнить читання журналів і погіршить роботу емулятора.", + "LoadApplicationFileTooltip": "Відкриває файловий провідник, щоб вибрати для завантаження сумісний файл Switch", + "LoadApplicationFolderTooltip": "Відкриває файловий провідник, щоб вибрати сумісну з комутатором розпаковану програму для завантаження", + "OpenRyujinxFolderTooltip": "Відкриває папку файлової системи Ryujinx", + "OpenRyujinxLogsTooltip": "Відкриває папку, куди записуються журнали", + "ExitTooltip": "Виходить з Ryujinx", + "OpenSettingsTooltip": "Відкриває вікно налаштувань", + "OpenProfileManagerTooltip": "Відкриває вікно диспетчера профілів користувачів", + "StopEmulationTooltip": "Зупиняє емуляцію поточної гри та повертається до вибору гри", + "CheckUpdatesTooltip": "Перевіряє наявність оновлень для Ryujinx", + "OpenAboutTooltip": "Відкриває вікно «Про програму».", + "GridSize": "Розмір сітки", + "GridSizeTooltip": "Змінити розмір елементів сітки", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "Португальська (Бразилія)", + "AboutRyujinxContributorsButtonHeader": "Переглянути всіх співавторів", + "SettingsTabSystemAudioVolume": "Гучність: ", + "AudioVolumeTooltip": "Змінити гучність звуку", + "SettingsTabSystemEnableInternetAccess": "Гостьовий доступ до Інтернету/режим LAN", + "EnableInternetAccessTooltip": "Дозволяє емульованій програмі підключатися до Інтернету.\n\nІгри з режимом локальної мережі можуть підключатися одна до одної, якщо це увімкнено, і системи підключені до однієї точки доступу. Сюди входять і справжні консолі.\n\nНЕ дозволяє підключатися до серверів Nintendo. Може призвести до збою в деяких іграх, які намагаються підключитися до Інтернету.\n\nЗалиште вимкненим, якщо не впевнені.", + "GameListContextMenuManageCheatToolTip": "Керування читами", + "GameListContextMenuManageCheat": "Керування читами", + "ControllerSettingsStickRange": "Діапазон:", + "DialogStopEmulationTitle": "Ryujinx - Зупинити емуляцію", + "DialogStopEmulationMessage": "Ви впевнені, що хочете зупинити емуляцію?", + "SettingsTabCpu": "ЦП", + "SettingsTabAudio": "Аудіо", + "SettingsTabNetwork": "Мережа", + "SettingsTabNetworkConnection": "Підключення до мережі", + "SettingsTabCpuCache": "Кеш ЦП", + "SettingsTabCpuMemory": "Пам'ять ЦП", + "DialogUpdaterFlatpakNotSupportedMessage": "Будь ласка, оновіть Ryujinx через FlatHub.", + "UpdaterDisabledWarningTitle": "Програму оновлення вимкнено!", + "GameListContextMenuOpenSdModsDirectory": "Відкрити каталог модифікацій Atmosphere", + "GameListContextMenuOpenSdModsDirectoryToolTip": "Відкриває альтернативний каталог SD-карти Atmosphere, який містить модифікації програми. Корисно для модифікацій, упакованих для реального обладнання.", + "ControllerSettingsRotate90": "Повернути на 90° за годинниковою стрілкою", + "IconSize": "Розмір значка", + "IconSizeTooltip": "Змінити розмір значків гри", + "MenuBarOptionsShowConsole": "Показати консоль", + "ShaderCachePurgeError": "Помилка очищення кешу шейдера {0}: {1}", + "UserErrorNoKeys": "Ключі не знайдено", + "UserErrorNoFirmware": "Прошивка не знайдена", + "UserErrorFirmwareParsingFailed": "Помилка аналізу прошивки", + "UserErrorApplicationNotFound": "Додаток не знайдено", + "UserErrorUnknown": "Невідома помилка", + "UserErrorUndefined": "Невизначена помилка", + "UserErrorNoKeysDescription": "Ryujinx не вдалося знайти ваш файл «prod.keys».", + "UserErrorNoFirmwareDescription": "Ryujinx не вдалося знайти встановлену прошивку", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx не вдалося проаналізувати прошивку. Зазвичай це спричинено застарілими ключами.", + "UserErrorApplicationNotFoundDescription": "Ryujinx не вдалося знайти дійсний додаток за вказаним шляхом", + "UserErrorUnknownDescription": "Сталася невідома помилка!", + "UserErrorUndefinedDescription": "Сталася невизначена помилка! Цього не повинно статися, зверніться до розробника!", + "OpenSetupGuideMessage": "Відкрити посібник із налаштування", + "NoUpdate": "Немає оновлень", + "TitleUpdateVersionLabel": "Версія {0} - {1}", + "RyujinxInfo": "Ryujin x - Інформація", + "RyujinxConfirm": "Ryujinx - Підтвердження", + "FileDialogAllTypes": "Всі типи", + "Never": "Ніколи", + "SwkbdMinCharacters": "Мінімальна кількість символів: {0}", + "SwkbdMinRangeCharacters": "Має бути {0}-{1} символів", + "SoftwareKeyboard": "Програмна клавіатура", + "DialogControllerAppletMessagePlayerRange": "Програма запитує {0} гравця(ів) з:\n\nТИПИ: {1}\n\nГРАВЦІ: {2}\n\n{3}Будь ласка, відкрийте «Налаштування» та повторно налаштуйте «Введення» або натисніть «Закрити».", + "DialogControllerAppletMessage": "Програма запитує рівно стільки гравців: {0} з:\n\nТИПАМИ: {1}\n\nГРАВЦІВ: {2}\n\n{3}Будь ласка, відкрийте «Налаштування» та повторно налаштуйте «Введення» або натисніть «Закрити».", + "DialogControllerAppletDockModeSet": "Встановлено режим док-станції. Ручний також недійсний.\n", + "UpdaterRenaming": "Перейменування старих файлів...", + "UpdaterRenameFailed": "Програмі оновлення не вдалося перейменувати файл: {0}", + "UpdaterAddingFiles": "Додавання нових файлів...", + "UpdaterExtracting": "Видобування оновлення...", + "UpdaterDownloading": "Завантаження оновлення...", + "Game": "Гра", + "Docked": "Док-станція", + "Handheld": "Портативний", + "ConnectionError": "Помилка з'єднання.", + "AboutPageDeveloperListMore": "{0} та інші...", + "ApiError": "Помилка API.", + "LoadingHeading": "Завантаження {0}", + "CompilingPPTC": "Компіляція PTC", + "CompilingShaders": "Компіляція шейдерів", + "AllKeyboards": "Всі клавіатури", + "OpenFileDialogTitle": "Виберіть підтримуваний файл для відкриття", + "OpenFolderDialogTitle": "Виберіть теку з розпакованою грою", + "AllSupportedFormats": "Усі підтримувані формати", + "RyujinxUpdater": "Програма оновлення Ryujinx", + "SettingsTabHotkeys": "Гарячі клавіші клавіатури", + "SettingsTabHotkeysHotkeys": "Гарячі клавіші клавіатури", + "SettingsTabHotkeysToggleVsyncHotkey": "Увімк/вимк вертикальну синхронізацію:", + "SettingsTabHotkeysScreenshotHotkey": "Знімок екрана:", + "SettingsTabHotkeysShowUiHotkey": "Показати інтерфейс:", + "SettingsTabHotkeysPauseHotkey": "Пауза:", + "SettingsTabHotkeysToggleMuteHotkey": "Вимкнути звук:", + "ControllerMotionTitle": "Налаштування керування рухом", + "ControllerRumbleTitle": "Налаштування вібрації", + "SettingsSelectThemeFileDialogTitle": "Виберіть файл теми", + "SettingsXamlThemeFile": "Файл теми Xaml", + "AvatarWindowTitle": "Керування обліковими записами - Аватар", + "Amiibo": "Amiibo", + "Unknown": "Невідомо", + "Usage": "Використання", + "Writable": "Можливість запису", + "SelectDlcDialogTitle": "Виберіть файли DLC", + "SelectUpdateDialogTitle": "Виберіть файли оновлення", + "UserProfileWindowTitle": "Менеджер профілів користувачів", + "CheatWindowTitle": "Менеджер читів", + "DlcWindowTitle": "Менеджер вмісту для завантаження", + "UpdateWindowTitle": "Менеджер оновлення назв", + "CheatWindowHeading": "Коди доступні для {0} [{1}]", + "DlcWindowHeading": "Вміст для завантаження, доступний для {1} ({2}): {0}", + "UserProfilesEditProfile": "Редагувати вибране", + "Cancel": "Скасувати", + "Save": "Зберегти", + "Discard": "Скасувати", + "UserProfilesSetProfileImage": "Встановити зображення профілю", + "UserProfileEmptyNameError": "Назва обов'язкова", + "UserProfileNoImageError": "Зображення профілю обов'язкове", + "GameUpdateWindowHeading": "{0} Доступні оновлення для {1} ({2})", + "SettingsTabHotkeysResScaleUpHotkey": "Збільшити роздільну здатність:", + "SettingsTabHotkeysResScaleDownHotkey": "Зменшити роздільну здатність:", + "UserProfilesName": "Ім'я", + "UserProfilesUserId": "ID користувача:", + "SettingsTabGraphicsBackend": "Графічний сервер", + "SettingsTabGraphicsBackendTooltip": "Графічний сервер для використання", + "SettingsEnableTextureRecompression": "Увімкнути рекомпресію текстури", + "SettingsEnableTextureRecompressionTooltip": "Стискає певні текстури, щоб зменшити використання VRAM.\n\nРекомендовано для використання з графічними процесорами, які мають менш ніж 4 ГБ відеопам’яті.\n\nЗалиште вимкненим, якщо не впевнені.", + "SettingsTabGraphicsPreferredGpu": "Бажаний GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "Виберіть відеокарту, яка використовуватиметься з графічним сервером Vulkan.\n\nНе впливає на графічний процесор, який використовуватиме OpenGL.\n\nЯкщо не впевнені, встановіть графічний процесор, позначений як «dGPU». Якщо такого немає, залиште це.", + "SettingsAppRequiredRestartMessage": "Необхідно перезапустити Ryujinx", + "SettingsGpuBackendRestartMessage": "Налаштування графічного сервера або GPU було змінено. Для цього знадобиться перезапуск", + "SettingsGpuBackendRestartSubMessage": "Ви хочете перезапустити зараз?", + "RyujinxUpdaterMessage": "Хочете оновити Ryujinx до останньої версії?", + "SettingsTabHotkeysVolumeUpHotkey": "Збільшити гучність:", + "SettingsTabHotkeysVolumeDownHotkey": "Зменшити гучність:", + "SettingsEnableMacroHLE": "Увімкнути макрос HLE", + "SettingsEnableMacroHLETooltip": "Високорівнева емуляція коду макросу GPU.\n\nПокращує продуктивність, але може викликати графічні збої в деяких іграх.\n\nЗалиште увімкненим, якщо не впевнені.", + "VolumeShort": "Гуч", + "UserProfilesManageSaves": "Керувати збереженнями", + "DeleteUserSave": "Ви хочете видалити збереження користувача для цієї гри?", + "IrreversibleActionNote": "Цю дію не можна скасувати.", + "SaveManagerHeading": "Керувати збереженнями для {0}", + "SaveManagerTitle": "Менеджер збереження", + "Name": "Назва", + "Size": "Розмір", + "Search": "Пошук", + "UserProfilesRecoverLostAccounts": "Відновлення втрачених облікових записів", + "Recover": "Відновити", + "UserProfilesRecoverHeading": "Знайдено збереження для наступних облікових записів" +} diff --git a/src/Ryujinx.Ava/Assets/Locales/zh_CN.json b/src/Ryujinx.Ava/Assets/Locales/zh_CN.json new file mode 100644 index 00000000..25dc3cba --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/zh_CN.json @@ -0,0 +1,614 @@ +{ + "Language": "简体中文", + "MenuBarFileOpenApplet": "打开小程序", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "打开独立的 Mii 小程序", + "SettingsTabInputDirectMouseAccess": "直通鼠标操作", + "SettingsTabSystemMemoryManagerMode": "内存管理模式:", + "SettingsTabSystemMemoryManagerModeSoftware": "软件", + "SettingsTabSystemMemoryManagerModeHost": "本机 (快速)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "跳过检查的本机 (最快)", + "MenuBarFile": "文件", + "MenuBarFileOpenFromFile": "加载文件", + "MenuBarFileOpenUnpacked": "加载解包后的游戏", + "MenuBarFileOpenEmuFolder": "打开 Ryujinx 文件夹", + "MenuBarFileOpenLogsFolder": "打开日志文件夹", + "MenuBarFileExit": "退出", + "MenuBarOptions": "选项", + "MenuBarOptionsToggleFullscreen": "切换全屏", + "MenuBarOptionsStartGamesInFullscreen": "全屏模式启动游戏", + "MenuBarOptionsStopEmulation": "停止模拟", + "MenuBarOptionsSettings": "设置", + "MenuBarOptionsManageUserProfiles": "管理用户账户", + "MenuBarActions": "操作", + "MenuBarOptionsSimulateWakeUpMessage": "模拟唤醒消息", + "MenuBarActionsScanAmiibo": "扫描 Amiibo", + "MenuBarTools": "工具", + "MenuBarToolsInstallFirmware": "安装固件", + "MenuBarFileToolsInstallFirmwareFromFile": "从 XCI 或 ZIP 安装固件", + "MenuBarFileToolsInstallFirmwareFromDirectory": "从文件夹安装固件", + "MenuBarHelp": "帮助", + "MenuBarHelpCheckForUpdates": "检查更新", + "MenuBarHelpAbout": "关于", + "MenuSearch": "搜索……", + "GameListHeaderFavorite": "收藏", + "GameListHeaderIcon": "图标", + "GameListHeaderApplication": "名称", + "GameListHeaderDeveloper": "制作商", + "GameListHeaderVersion": "版本", + "GameListHeaderTimePlayed": "游玩时长", + "GameListHeaderLastPlayed": "最近游玩", + "GameListHeaderFileExtension": "扩展名", + "GameListHeaderFileSize": "大小", + "GameListHeaderPath": "路径", + "GameListContextMenuOpenUserSaveDirectory": "打开应用存档目录", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "打开储存游戏存档的目录", + "GameListContextMenuOpenDeviceSaveDirectory": "打开应用系统目录", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "打开包含游戏系统设置的目录", + "GameListContextMenuOpenBcatSaveDirectory": "打开 BCAT 目录", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "打开包含游戏 BCAT 数据的目录", + "GameListContextMenuManageTitleUpdates": "管理游戏更新", + "GameListContextMenuManageTitleUpdatesToolTip": "打开更新管理器", + "GameListContextMenuManageDlc": "管理 DLC", + "GameListContextMenuManageDlcToolTip": "打开 DLC 管理窗口", + "GameListContextMenuOpenModsDirectory": "打开 MOD 目录", + "GameListContextMenuOpenModsDirectoryToolTip": "打开存放游戏 MOD 的目录", + "GameListContextMenuCacheManagement": "缓存管理", + "GameListContextMenuCacheManagementPurgePptc": "清除已编译的 PPTC 文件", + "GameListContextMenuCacheManagementPurgePptcToolTip": "仅删除 PPTC 转换后的文件,下次打开游戏时将根据 .info 文件重新生成 PPTC 文件。\n如想彻底清除缓存,请进入目录把 .info 文件一并删除", + "GameListContextMenuCacheManagementPurgeShaderCache": "清除着色器缓存", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "删除游戏的着色器缓存", + "GameListContextMenuCacheManagementOpenPptcDirectory": "打开 PPTC 目录", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "打开包含游戏 PPTC 缓存的目录", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "打开着色器缓存目录", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "打开包含应用程序着色器缓存的目录", + "GameListContextMenuExtractData": "提取数据", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "从游戏的当前状态中提取 ExeFS 分区 (包括更新)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "从游戏的当前状态中提取 RomFS 分区 (包括更新)", + "GameListContextMenuExtractDataLogo": "图标", + "GameListContextMenuExtractDataLogoToolTip": "从游戏的当前状态中提取图标 (包括更新)", + "StatusBarGamesLoaded": "{0}/{1} 游戏加载完成", + "StatusBarSystemVersion": "系统版本:{0}", + "Settings": "设置", + "SettingsTabGeneral": "用户界面", + "SettingsTabGeneralGeneral": "常规", + "SettingsTabGeneralEnableDiscordRichPresence": "启用 Discord 在线状态展示", + "SettingsTabGeneralCheckUpdatesOnLaunch": "自动检查更新", + "SettingsTabGeneralShowConfirmExitDialog": "显示 \"确认退出\" 对话框", + "SettingsTabGeneralHideCursorOnIdle": "自动隐藏鼠标", + "SettingsTabGeneralGameDirectories": "游戏目录", + "SettingsTabGeneralAdd": "添加", + "SettingsTabGeneralRemove": "删除", + "SettingsTabSystem": "系统", + "SettingsTabSystemCore": "核心", + "SettingsTabSystemSystemRegion": "系统区域:", + "SettingsTabSystemSystemRegionJapan": "日本", + "SettingsTabSystemSystemRegionUSA": "美国", + "SettingsTabSystemSystemRegionEurope": "欧洲", + "SettingsTabSystemSystemRegionAustralia": "澳大利亚", + "SettingsTabSystemSystemRegionChina": "中国", + "SettingsTabSystemSystemRegionKorea": "韩国", + "SettingsTabSystemSystemRegionTaiwan": "台湾地区", + "SettingsTabSystemSystemLanguage": "系统语言:", + "SettingsTabSystemSystemLanguageJapanese": "日语", + "SettingsTabSystemSystemLanguageAmericanEnglish": "美式英语", + "SettingsTabSystemSystemLanguageFrench": "法语", + "SettingsTabSystemSystemLanguageGerman": "德语", + "SettingsTabSystemSystemLanguageItalian": "意大利语", + "SettingsTabSystemSystemLanguageSpanish": "西班牙语", + "SettingsTabSystemSystemLanguageChinese": "中文(简体)——无效", + "SettingsTabSystemSystemLanguageKorean": "韩语", + "SettingsTabSystemSystemLanguageDutch": "荷兰语", + "SettingsTabSystemSystemLanguagePortuguese": "葡萄牙语", + "SettingsTabSystemSystemLanguageRussian": "俄语", + "SettingsTabSystemSystemLanguageTaiwanese": "中文(繁体)——无效", + "SettingsTabSystemSystemLanguageBritishEnglish": "英式英语", + "SettingsTabSystemSystemLanguageCanadianFrench": "加拿大法语", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "拉美西班牙语", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "简体中文(推荐)", + "SettingsTabSystemSystemLanguageTraditionalChinese": "繁体中文(推荐)", + "SettingsTabSystemSystemTimeZone": "系统时区:", + "SettingsTabSystemSystemTime": "系统时钟:", + "SettingsTabSystemEnableVsync": "启用垂直同步", + "SettingsTabSystemEnablePptc": "开启 PPTC 缓存", + "SettingsTabSystemEnableFsIntegrityChecks": "文件系统完整性检查", + "SettingsTabSystemAudioBackend": "音频后端:", + "SettingsTabSystemAudioBackendDummy": "无", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "修正", + "SettingsTabSystemHacksNote": " (会引起模拟器不稳定)", + "SettingsTabSystemExpandDramSize": "使用开发机的内存布局", + "SettingsTabSystemIgnoreMissingServices": "忽略缺失的服务", + "SettingsTabGraphics": "图形", + "SettingsTabGraphicsAPI": "图形 API", + "SettingsTabGraphicsEnableShaderCache": "启用着色器缓存", + "SettingsTabGraphicsAnisotropicFiltering": "各向异性过滤:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "自动", + "SettingsTabGraphicsAnisotropicFiltering2x": "2x", + "SettingsTabGraphicsAnisotropicFiltering4x": "4x", + "SettingsTabGraphicsAnisotropicFiltering8x": "8x", + "SettingsTabGraphicsAnisotropicFiltering16x": "16x", + "SettingsTabGraphicsResolutionScale": "分辨率缩放:", + "SettingsTabGraphicsResolutionScaleCustom": "自定义 (不推荐)", + "SettingsTabGraphicsResolutionScaleNative": "原生 (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "宽高比:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "拉伸至屏幕", + "SettingsTabGraphicsDeveloperOptions": "开发者选项", + "SettingsTabGraphicsShaderDumpPath": "图形着色器转储路径:", + "SettingsTabLogging": "日志", + "SettingsTabLoggingLogging": "日志", + "SettingsTabLoggingEnableLoggingToFile": "保存日志为文件", + "SettingsTabLoggingEnableStubLogs": "记录Stub", + "SettingsTabLoggingEnableInfoLogs": "记录Info", + "SettingsTabLoggingEnableWarningLogs": "记录Warning", + "SettingsTabLoggingEnableErrorLogs": "记录Error", + "SettingsTabLoggingEnableTraceLogs": "记录Trace", + "SettingsTabLoggingEnableGuestLogs": "记录Guest", + "SettingsTabLoggingEnableFsAccessLogs": "记录文件访问", + "SettingsTabLoggingFsGlobalAccessLogMode": "记录全局文件访问模式:", + "SettingsTabLoggingDeveloperOptions": "开发者选项 (警告: 会降低性能)", + "SettingsTabLoggingGraphicsBackendLogLevel": "图形后端日志级别:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "无", + "SettingsTabLoggingGraphicsBackendLogLevelError": "错误", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "减速", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "全部", + "SettingsTabLoggingEnableDebugLogs": "启用调试日志", + "SettingsTabInput": "输入", + "SettingsTabInputEnableDockedMode": "主机模式", + "SettingsTabInputDirectKeyboardAccess": "直通键盘控制", + "SettingsButtonSave": "保存", + "SettingsButtonClose": "取消", + "SettingsButtonOk": "保存", + "SettingsButtonCancel": "取消", + "SettingsButtonApply": "应用", + "ControllerSettingsPlayer": "玩家", + "ControllerSettingsPlayer1": "玩家 1", + "ControllerSettingsPlayer2": "玩家 2", + "ControllerSettingsPlayer3": "玩家 3", + "ControllerSettingsPlayer4": "玩家 4", + "ControllerSettingsPlayer5": "玩家 5", + "ControllerSettingsPlayer6": "玩家 6", + "ControllerSettingsPlayer7": "玩家 7", + "ControllerSettingsPlayer8": "玩家 8", + "ControllerSettingsHandheld": "掌机模式", + "ControllerSettingsInputDevice": "输入设备", + "ControllerSettingsRefresh": "刷新", + "ControllerSettingsDeviceDisabled": "关闭", + "ControllerSettingsControllerType": "手柄类型", + "ControllerSettingsControllerTypeHandheld": "掌机", + "ControllerSettingsControllerTypeProController": "Pro 手柄", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "左 JoyCon", + "ControllerSettingsControllerTypeJoyConRight": "右 JoyCon", + "ControllerSettingsProfile": "预设", + "ControllerSettingsProfileDefault": "默认布局", + "ControllerSettingsLoad": "加载", + "ControllerSettingsAdd": "新建", + "ControllerSettingsRemove": "删除", + "ControllerSettingsButtons": "按钮", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "方向键", + "ControllerSettingsDPadUp": "上", + "ControllerSettingsDPadDown": "下", + "ControllerSettingsDPadLeft": "左", + "ControllerSettingsDPadRight": "右", + "ControllerSettingsLStick": "左摇杆", + "ControllerSettingsLStickButton": "按下摇杆", + "ControllerSettingsLStickUp": "上", + "ControllerSettingsLStickDown": "下", + "ControllerSettingsLStickLeft": "左", + "ControllerSettingsLStickRight": "右", + "ControllerSettingsLStickStick": "摇杆", + "ControllerSettingsLStickInvertXAxis": "反转 X 方向", + "ControllerSettingsLStickInvertYAxis": "反转 Y 方向", + "ControllerSettingsLStickDeadzone": "死区:", + "ControllerSettingsRStick": "右摇杆", + "ControllerSettingsRStickButton": "按下摇杆", + "ControllerSettingsRStickUp": "上", + "ControllerSettingsRStickDown": "下", + "ControllerSettingsRStickLeft": "左", + "ControllerSettingsRStickRight": "右", + "ControllerSettingsRStickStick": "摇杆", + "ControllerSettingsRStickInvertXAxis": "反转 X 方向", + "ControllerSettingsRStickInvertYAxis": "反转 Y 方向", + "ControllerSettingsRStickDeadzone": "死区:", + "ControllerSettingsTriggersLeft": "左扳机", + "ControllerSettingsTriggersRight": "右扳机", + "ControllerSettingsTriggersButtonsLeft": "左扳机键", + "ControllerSettingsTriggersButtonsRight": "右扳机键", + "ControllerSettingsTriggers": "扳机", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "左背键", + "ControllerSettingsExtraButtonsRight": "右背键", + "ControllerSettingsMisc": "其他", + "ControllerSettingsTriggerThreshold": "扳机阈值:", + "ControllerSettingsMotion": "体感", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "使用 CemuHook 体感协议", + "ControllerSettingsMotionControllerSlot": "手柄:", + "ControllerSettingsMotionMirrorInput": "镜像操作", + "ControllerSettingsMotionRightJoyConSlot": "右JoyCon:", + "ControllerSettingsMotionServerHost": "服务器 Host:", + "ControllerSettingsMotionGyroSensitivity": "陀螺仪敏感度:", + "ControllerSettingsMotionGyroDeadzone": "陀螺仪死区:", + "ControllerSettingsSave": "保存", + "ControllerSettingsClose": "关闭", + "UserProfilesSelectedUserProfile": "选择的用户账户:", + "UserProfilesSaveProfileName": "保存名称", + "UserProfilesChangeProfileImage": "更换头像", + "UserProfilesAvailableUserProfiles": "现有账户:", + "UserProfilesAddNewProfile": "新建账户", + "UserProfilesDeleteSelectedProfile": "删除选中账户", + "UserProfilesClose": "关闭", + "ProfileImageSelectionTitle": "头像选择", + "ProfileImageSelectionHeader": "选择合适的头像图片", + "ProfileImageSelectionNote": "您可以导入自定义头像,或从系统中选择头像", + "ProfileImageSelectionImportImage": "导入图像文件", + "ProfileImageSelectionSelectAvatar": "选择系统头像", + "InputDialogTitle": "输入对话框", + "InputDialogOk": "完成", + "InputDialogCancel": "取消", + "InputDialogAddNewProfileTitle": "选择用户名称", + "InputDialogAddNewProfileHeader": "请输入账户名称", + "InputDialogAddNewProfileSubtext": "(最大长度: {0})", + "AvatarChoose": "选择", + "AvatarSetBackgroundColor": "设置背景色", + "AvatarClose": "关闭", + "ControllerSettingsLoadProfileToolTip": "加载预设", + "ControllerSettingsAddProfileToolTip": "新增预设", + "ControllerSettingsRemoveProfileToolTip": "删除预设", + "ControllerSettingsSaveProfileToolTip": "保存预设", + "MenuBarFileToolsTakeScreenshot": "保存截图", + "MenuBarFileToolsHideUi": "Hide UI", + "GameListContextMenuToggleFavorite": "收藏", + "GameListContextMenuToggleFavoriteToolTip": "标记喜爱的游戏", + "SettingsTabGeneralTheme": "主题", + "SettingsTabGeneralThemeCustomTheme": "自选主题路径", + "SettingsTabGeneralThemeBaseStyle": "主题色调", + "SettingsTabGeneralThemeBaseStyleDark": "暗黑", + "SettingsTabGeneralThemeBaseStyleLight": "浅色", + "SettingsTabGeneralThemeEnableCustomTheme": "使用自选主题界面", + "ButtonBrowse": "浏览", + "ControllerSettingsConfigureGeneral": "配置", + "ControllerSettingsRumble": "震动", + "ControllerSettingsRumbleStrongMultiplier": "强震动幅度", + "ControllerSettingsRumbleWeakMultiplier": "弱震动幅度", + "DialogMessageSaveNotAvailableMessage": "没有{0} [{1:x16}]的游戏存档", + "DialogMessageSaveNotAvailableCreateSaveMessage": "是否创建该游戏的存档文件夹?", + "DialogConfirmationTitle": "Ryujinx - 设置", + "DialogUpdaterTitle": "Ryujinx - 更新", + "DialogErrorTitle": "Ryujinx - 错误", + "DialogWarningTitle": "Ryujinx - 警告", + "DialogExitTitle": "Ryujinx - 关闭", + "DialogErrorMessage": "Ryujinx 发生错误", + "DialogExitMessage": "是否关闭 Ryujinx?", + "DialogExitSubMessage": "未保存的进度会丢失", + "DialogMessageCreateSaveErrorMessage": "创建特定的存档时出错: {0}", + "DialogMessageFindSaveErrorMessage": "查找特定的存档时出错: {0}", + "FolderDialogExtractTitle": "选择要解压到的文件夹", + "DialogNcaExtractionMessage": "提取 {1} 的 {0} 分区...", + "DialogNcaExtractionTitle": "Ryujinx - NCA分区提取", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "提取失败。所选文件中不含主NCA文件", + "DialogNcaExtractionCheckLogErrorMessage": "提取失败。请查看日志文件获取详情。", + "DialogNcaExtractionSuccessMessage": "提取成功。", + "DialogUpdaterConvertFailedMessage": "无法转换当前 Ryujinx 版本。", + "DialogUpdaterCancelUpdateMessage": "更新取消!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "您使用的 Ryujinx 是最新版本。", + "DialogUpdaterFailedToGetVersionMessage": "尝试从 Github 获取版本信息时无效。\n可能由于 GitHub Actions 正在编译新版本。请过几分钟重试。", + "DialogUpdaterConvertFailedGithubMessage": "无法转换从 Github 接收到的 Ryujinx 版本。", + "DialogUpdaterDownloadingMessage": "下载新版本中...", + "DialogUpdaterExtractionMessage": "正在提取更新...", + "DialogUpdaterRenamingMessage": "正在删除旧文件...", + "DialogUpdaterAddingFilesMessage": "安装更新中...", + "DialogUpdaterCompleteMessage": "更新成功!", + "DialogUpdaterRestartMessage": "立即重启 Ryujinx 完成更新?", + "DialogUpdaterArchNotSupportedMessage": "您运行的系统架构不受支持!", + "DialogUpdaterArchNotSupportedSubMessage": "(仅支持 x64 系统)", + "DialogUpdaterNoInternetMessage": "没有连接到互联网", + "DialogUpdaterNoInternetSubMessage": "请确保互联网连接正常。", + "DialogUpdaterDirtyBuildMessage": "不能更新非官方版本的 Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "如果希望使用受支持的版本,请您在 https://ryujinx.org/ 下载。", + "DialogRestartRequiredMessage": "需要重启模拟器", + "DialogThemeRestartMessage": "主题设置已保存。需要重新启动才能生效。", + "DialogThemeRestartSubMessage": "您是否要重启?", + "DialogFirmwareInstallEmbeddedMessage": "要安装游戏内置的固件吗?(固件 {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安装的固件,但 Ryujinx 可以从现有的游戏安装固件{0}.\n模拟器现在可以运行。", + "DialogFirmwareNoFirmwareInstalledMessage": "未安装固件", + "DialogFirmwareInstalledMessage": "已安装固件{0}", + "DialogOpenSettingsWindowLabel": "打开设置窗口", + "DialogControllerAppletTitle": "控制器小窗口", + "DialogMessageDialogErrorExceptionMessage": "显示消息对话框时出错: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "显示软件键盘时出错: {0}", + "DialogErrorAppletErrorExceptionMessage": "显示错误对话框时出错: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\n有关修复此错误的更多信息,可以遵循我们的设置指南。", + "DialogUserErrorDialogTitle": "Ryujinx 错误 ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "从 API 获取信息时出错。", + "DialogAmiiboApiConnectErrorMessage": "无法连接到 Amiibo API 服务器。服务器可能已关闭,或者您没有连接网络。", + "DialogProfileInvalidProfileErrorMessage": "预设{0} 与当前输入配置系统不兼容。", + "DialogProfileDefaultProfileOverwriteErrorMessage": "默认预设不能被覆盖", + "DialogProfileDeleteProfileTitle": "删除预设", + "DialogProfileDeleteProfileMessage": "删除后不可恢复,确认删除吗?", + "DialogWarning": "警告", + "DialogPPTCDeletionMessage": "您即将删除:\n\n{0}的 PPTC 缓存\n\n确定吗?", + "DialogPPTCDeletionErrorMessage": "清除位于{0}的 PPTC 缓存时出错: {1}", + "DialogShaderDeletionMessage": "您即将删除:\n\n{0}的着色器缓存\n\n确定吗?", + "DialogShaderDeletionErrorMessage": "清除位于{0}的着色器缓存时出错: {1}", + "DialogRyujinxErrorMessage": "Ryujinx 遇到错误", + "DialogInvalidTitleIdErrorMessage": "UI 错误:所选游戏没有有效的标题ID", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "路径{0}找不到有效的系统固件。", + "DialogFirmwareInstallerFirmwareInstallTitle": "固件{0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "即将安装系统版本{0}。", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n会替换当前系统版本{0}。", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n是否确认继续?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "安装固件中...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "成功安装系统版本{0}。", + "DialogUserProfileDeletionWarningMessage": "删除后将没有可选择的用户账户", + "DialogUserProfileDeletionConfirmMessage": "是否删除选择的账户", + "DialogControllerSettingsModifiedConfirmMessage": "目前的输入预设已更新", + "DialogControllerSettingsModifiedConfirmSubMessage": "要保存吗?", + "DialogLoadNcaErrorMessage": "{0}. 错误的文件: {1}", + "DialogDlcNoDlcErrorMessage": "选择的文件不包含所选游戏的 DLC!", + "DialogPerformanceCheckLoggingEnabledMessage": "您启用了跟踪日志,仅供开发人员使用。", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "为了获得最佳性能,建议禁用跟踪日志记录。您是否要立即禁用?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "您启用了着色器转储,仅供开发人员使用。", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "为了获得最佳性能,建议禁用着色器转储。您是否要立即禁用?", + "DialogLoadAppGameAlreadyLoadedMessage": "已有游戏正在运行", + "DialogLoadAppGameAlreadyLoadedSubMessage": "请停止模拟或关闭程序,再启动另一个游戏。", + "DialogUpdateAddUpdateErrorMessage": "选择的文件不包含所选游戏的更新!", + "DialogSettingsBackendThreadingWarningTitle": "警告 - 后端多线程", + "DialogSettingsBackendThreadingWarningMessage": "改变此选项后必须重启 Ryujinx 才能生效。\n\n取决于您的硬件,可能需要手动禁用驱动面板中的线程优化。", + "SettingsTabGraphicsFeaturesOptions": "功能", + "SettingsTabGraphicsBackendMultithreading": "后端多线程:", + "CommonAuto": "自动(推荐)", + "CommonOff": "关闭", + "CommonOn": "打开", + "InputDialogYes": "是", + "InputDialogNo": "否", + "DialogProfileInvalidProfileNameErrorMessage": "文件名包含无效字符,请重试。", + "MenuBarOptionsPauseEmulation": "暂停", + "MenuBarOptionsResumeEmulation": "继续", + "AboutUrlTooltipMessage": "在浏览器中打开 Ryujinx 的官网。", + "AboutDisclaimerMessage": "Ryujinx 以任何方式与Nintendo™及其任何商业伙伴都没有关联", + "AboutAmiiboDisclaimerMessage": "我们的 Amiibo 模拟使用了\nAmiiboAPI (www.amiiboapi.com) ", + "AboutPatreonUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Patreon 赞助页。", + "AboutGithubUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 GitHub 代码库。", + "AboutDiscordUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Discord 邀请链接。", + "AboutTwitterUrlTooltipMessage": "在浏览器中打开 Ryujinx 的 Twitter 主页。", + "AboutRyujinxAboutTitle": "关于:", + "AboutRyujinxAboutContent": "Ryujinx是一款Nintendo Switch™模拟器。\n您可以在 Patreon 上赞助 Ryujinx。\n关注 Twitter 或 Discord 可以获取模拟器最新动态。\n如果您对开发感兴趣,欢迎来 GitHub 和 Discord 加入我们!", + "AboutRyujinxMaintainersTitle": "由以下作者维护:", + "AboutRyujinxMaintainersContentTooltipMessage": "在浏览器中打开贡献者的网页", + "AboutRyujinxSupprtersTitle": "感谢 Patreon 的赞助者:", + "AmiiboSeriesLabel": "Amiibo 系列", + "AmiiboCharacterLabel": "角色", + "AmiiboScanButtonLabel": "扫描", + "AmiiboOptionsShowAllLabel": "显示所有 Amiibo 系列", + "AmiiboOptionsUsRandomTagLabel": "修复:使用随机标记的 UUID", + "DlcManagerTableHeadingEnabledLabel": "启用", + "DlcManagerTableHeadingTitleIdLabel": "游戏ID", + "DlcManagerTableHeadingContainerPathLabel": "文件夹路径", + "DlcManagerTableHeadingFullPathLabel": "完整路径", + "DlcManagerRemoveAllButton": "全部删除", + "DlcManagerEnableAllButton": "全部选择", + "DlcManagerDisableAllButton": "全部禁用", + "MenuBarOptionsChangeLanguage": "更改语言", + "CommonSort": "排序", + "CommonShowNames": "显示名称", + "CommonFavorite": "收藏", + "OrderAscending": "从小到大", + "OrderDescending": "从大到小", + "SettingsTabGraphicsFeatures": "功能与增强", + "ErrorWindowTitle": "错误窗口", + "ToggleDiscordTooltip": "控制是否在 Discord 中显示您的游玩状态", + "AddGameDirBoxTooltip": "输入要添加的游戏目录", + "AddGameDirTooltip": "添加游戏目录到列表中", + "RemoveGameDirTooltip": "移除选中的目录", + "CustomThemeCheckTooltip": "使用自定义UI主题来更改模拟器的外观样式", + "CustomThemePathTooltip": "自定义主题的目录", + "CustomThemeBrowseTooltip": "查找自定义主题", + "DockModeToggleTooltip": "启用 Switch 的主机模式。\n绝大多数游戏画质会提高,略微增加性能消耗。\n在掌机和主机模式切换的过程中,您可能需要重新设置手柄类型", + "DirectKeyboardTooltip": "开启 \"直连键盘访问(HID)支持\"\n(部分游戏可以使用您的键盘输入文字)", + "DirectMouseTooltip": "开启 \"直连鼠标访问(HID)支持\"\n(部分游戏可以使用您的鼠标导航)", + "RegionTooltip": "更改系统区域", + "LanguageTooltip": "更改系统语言", + "TimezoneTooltip": "更改系统时区", + "TimeTooltip": "更改系统时钟", + "VSyncToggleTooltip": "关闭后,小部分游戏可以超过60FPS帧率,以获得高帧率体验。\n但是可能出现软锁或读盘时间增加。\n如不确定或没有需求,请保持选项开启", + "PptcToggleTooltip": "缓存编译完成的游戏CPU指令。减少启动时间和卡顿,提高游戏响应速度", + "FsIntegrityToggleTooltip": "检查游戏文件内容的完整性。\n遇到损坏的文件则记录到日志文件,有助于排查错误。\n对性能没有影响。", + "AudioBackendTooltip": "默认推荐SDL,但每种音频后端对各类游戏兼容性不同,遇到音频问题可以尝试切换后端", + "MemoryManagerTooltip": "改变 Switch 内存映射到电脑内存的方式,会影响CPU性能消耗", + "MemoryManagerSoftwareTooltip": "使用软件内存页管理,最精确但是速度最慢", + "MemoryManagerHostTooltip": "直接映射内存页到电脑内存,JIT效率高", + "MemoryManagerUnsafeTooltip": "直接映射内存页,但不检查内存溢出,JIT效率最高。\nRyujinx可以访问任何位置的内存,因而相对不安全。\n此模式下只应运行您信任的游戏或软件(即官方游戏)", + "DRamTooltip": "使用Switch开发机的内存布局。\n不会提高任何性能,某些高清纹理包或 4k 分辨率 MOD 可能需要此选项。\n如果不确定,请始终关闭该选项。", + "IgnoreMissingServicesTooltip": "开启后,游戏会忽略未实现的系统服务,从而继续运行。\n少部分新发布的游戏由于使用新的未知系统服务,可能需要此选项来避免闪退。\n模拟器更新完善系统服务之后,则无需开启选项。\n如您的游戏已经正常运行,请保持此选项关闭", + "GraphicsBackendThreadingTooltip": "启用后端多线程", + "GalThreadingTooltip": "使用模拟器内置的多线程优化,减少着色器编译的卡顿,并提高驱动程序的性能(尤其是缺失多线程的AMD)。\nNVIDIA显卡需要重启模拟器才能禁用驱动本身的线程优化,您也可以手动在控制面板将其禁用", + "ShaderCacheToggleTooltip": "开启后,模拟器会保存编译完成的着色器到磁盘,减少游戏渲染新特效和场景时的卡顿", + "ResolutionScaleTooltip": "缩放渲染的分辨率", + "ResolutionScaleEntryTooltip": "尽可能使用例如1.5的浮点倍数。非整数的倍率易引起 BUG", + "AnisotropyTooltip": "各向异性过滤等级。提高倾斜视角纹理的清晰度\n('自动'使用游戏默认的等级)", + "AspectRatioTooltip": "渲染窗口的宽高比", + "ShaderDumpPathTooltip": "转储图形着色器的路径", + "FileLogTooltip": "是否保存日志文件到硬盘", + "StubLogTooltip": "记录Stub消息", + "InfoLogTooltip": "记录Info消息", + "WarnLogTooltip": "记录Warning消息", + "ErrorLogTooltip": "记录Error消息", + "TraceLogTooltip": "记录Trace消息", + "GuestLogTooltip": "记录Guest消息", + "FileAccessLogTooltip": "记录文件访问消息", + "FSAccessLogModeTooltip": "记录FS访问消息,输出到控制台。可选的模式是0-3", + "DeveloperOptionTooltip": "使用请谨慎", + "OpenGlLogLevel": "需要打开适当的日志等级", + "DebugLogTooltip": "记录Debug消息", + "LoadApplicationFileTooltip": "选择 Switch 支持的游戏格式并加载", + "LoadApplicationFolderTooltip": "选择解包后的 Switch 游戏并加载", + "OpenRyujinxFolderTooltip": "打开 Ryujinx 系统目录", + "OpenRyujinxLogsTooltip": "打开日志存放的目录", + "ExitTooltip": "关闭 Ryujinx", + "OpenSettingsTooltip": "打开设置窗口", + "OpenProfileManagerTooltip": "打开用户账户管理界面", + "StopEmulationTooltip": "停止运行当前游戏并回到主界面", + "CheckUpdatesTooltip": "检查 Ryujinx 新版本", + "OpenAboutTooltip": "打开'关于'窗口", + "GridSize": "网格尺寸", + "GridSizeTooltip": "调整网格模式的大小", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "巴西葡萄牙语", + "AboutRyujinxContributorsButtonHeader": "查看所有参与者", + "SettingsTabSystemAudioVolume": "音量: ", + "AudioVolumeTooltip": "调节音量", + "SettingsTabSystemEnableInternetAccess": "允许网络访问/局域网模式", + "EnableInternetAccessTooltip": "允许模拟的游戏进程访问互联网。\n当多个模拟器/真实Switch连接到同一个局域网时,带有 LAN 模式的游戏可以相互通信。\n即使开启选项也无法访问 Nintendo 服务器。此外可能导致某些尝试联网的游戏崩溃。\n如果您不确定,请关闭该选项。", + "GameListContextMenuManageCheatToolTip": "管理金手指", + "GameListContextMenuManageCheat": "管理金手指", + "ControllerSettingsStickRange": "范围", + "DialogStopEmulationTitle": "Ryujinx - 停止模拟", + "DialogStopEmulationMessage": "是否确定停止模拟?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "音频", + "SettingsTabNetwork": "网络", + "SettingsTabNetworkConnection": "网络连接", + "SettingsTabCpuCache": "CPU 缓存", + "SettingsTabCpuMemory": "CPU 内存", + "DialogUpdaterFlatpakNotSupportedMessage": "请通过 FlatHub 更新 Ryujinx。", + "UpdaterDisabledWarningTitle": "更新已禁用!", + "GameListContextMenuOpenSdModsDirectory": "打开 Atmosphere MOD 目录", + "GameListContextMenuOpenSdModsDirectoryToolTip": "打开适用于 Atmosphere 自制系统的 MOD 目录", + "ControllerSettingsRotate90": "顺时针旋转 90°", + "IconSize": "图标尺寸", + "IconSizeTooltip": "更改游戏图标大小", + "MenuBarOptionsShowConsole": "显示控制台", + "ShaderCachePurgeError": "清除着色器缓存时出错: {0}: {1}", + "UserErrorNoKeys": "找不到密钥", + "UserErrorNoFirmware": "找不到固件", + "UserErrorFirmwareParsingFailed": "固件解析错误", + "UserErrorApplicationNotFound": "找不到应用程序", + "UserErrorUnknown": "未知错误", + "UserErrorUndefined": "未定义错误", + "UserErrorNoKeysDescription": "Ryujinx 找不到 'prod.keys' 文件", + "UserErrorNoFirmwareDescription": "Ryujinx 找不到任何已安装的固件", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx 无法解密选择的固件。这通常是由于过旧的密钥。", + "UserErrorApplicationNotFoundDescription": "Ryujinx 在选中路径找不到有效的应用程序。", + "UserErrorUnknownDescription": "发生未知错误!", + "UserErrorUndefinedDescription": "发生了未定义错误!此类错误不应出现,请联系开发人员!", + "OpenSetupGuideMessage": "打开设置教程", + "NoUpdate": "无更新", + "TitleUpdateVersionLabel": "版本 {0} - {1}", + "RyujinxInfo": "Ryujinx - 信息", + "RyujinxConfirm": "Ryujinx - 确认", + "FileDialogAllTypes": "全部类型", + "Never": "从未", + "SwkbdMinCharacters": "至少应为 {0} 个字长", + "SwkbdMinRangeCharacters": "必须为 {0}-{1} 个字长", + "SoftwareKeyboard": "软件键盘", + "DialogControllerAppletMessagePlayerRange": "游戏需要 {0} 个玩家并满足以下要求:\n\n手柄类型:{1}\n\n玩家类型:{2}\n\n{3}请打开设置窗口,重新配置手柄输入;或者关闭返回。", + "DialogControllerAppletMessage": "游戏需要刚好 {0} 个玩家并满足以下要求:\n\n手柄类型:{1}\n\n玩家类型:{2}\n\n{3}请打开设置窗口,重新配置手柄输入;或者关闭返回。", + "DialogControllerAppletDockModeSet": "目前处于主机模式,无法使用掌机操作方式", + "UpdaterRenaming": "正在删除旧文件...", + "UpdaterRenameFailed": "更新过程中无法重命名文件: {0}", + "UpdaterAddingFiles": "安装更新中...", + "UpdaterExtracting": "正在提取更新...", + "UpdaterDownloading": "下载新版本中...", + "Game": "游戏", + "Docked": "主机模式", + "Handheld": "掌机模式", + "ConnectionError": "连接错误。", + "AboutPageDeveloperListMore": "{0} 等开发者...", + "ApiError": "API错误。", + "LoadingHeading": "正在启动 {0}", + "CompilingPPTC": "编译PPTC缓存中", + "CompilingShaders": "编译着色器中", + "AllKeyboards": "所有键盘", + "OpenFileDialogTitle": "选择支持的文件格式", + "OpenFolderDialogTitle": "选择一个包含解包游戏的文件夹", + "AllSupportedFormats": "所有支持的格式", + "RyujinxUpdater": "Ryujinx 更新程序", + "SettingsTabHotkeys": "快捷键", + "SettingsTabHotkeysHotkeys": "键盘快捷键", + "SettingsTabHotkeysToggleVsyncHotkey": "切换垂直同步", + "SettingsTabHotkeysScreenshotHotkey": "截屏", + "SettingsTabHotkeysShowUiHotkey": "隐藏UI", + "SettingsTabHotkeysPauseHotkey": "暂停", + "SettingsTabHotkeysToggleMuteHotkey": "静音", + "ControllerMotionTitle": "体感操作设置", + "ControllerRumbleTitle": "震动设置", + "SettingsSelectThemeFileDialogTitle": "选择主题文件", + "SettingsXamlThemeFile": "Xaml 主题文件", + "AvatarWindowTitle": "管理账户 - 头像", + "Amiibo": "Amiibo", + "Unknown": "未知", + "Usage": "扫描可获得", + "Writable": "可写入", + "SelectDlcDialogTitle": "选择 DLC 文件", + "SelectUpdateDialogTitle": "选择更新文件", + "UserProfileWindowTitle": "管理用户账户", + "CheatWindowTitle": "金手指管理器", + "DlcWindowTitle": "管理游戏 DLC", + "UpdateWindowTitle": "游戏更新管理器", + "CheatWindowHeading": "适用于 {0} [{1}] 的金手指", + "DlcWindowHeading": "{0} 个适用于 {1} ({2}) 的 DLC", + "UserProfilesEditProfile": "编辑选中账户", + "Cancel": "取消", + "Save": "保存", + "Discard": "返回", + "UserProfilesSetProfileImage": "选择头像", + "UserProfileEmptyNameError": "必须输入名称", + "UserProfileNoImageError": "请选择您的头像", + "GameUpdateWindowHeading": "{0} 个适用于 {1} ({2}) 的更新", + "SettingsTabHotkeysResScaleUpHotkey": "提高分辨率:", + "SettingsTabHotkeysResScaleDownHotkey": "降低分辨率:", + "UserProfilesName": "名称:", + "UserProfilesUserId": "用户 ID:", + "SettingsTabGraphicsBackend": "图形后端", + "SettingsTabGraphicsBackendTooltip": "显卡使用的图形后端", + "SettingsEnableTextureRecompression": "启用纹理重压缩", + "SettingsEnableTextureRecompressionTooltip": "压缩某些纹理以减少显存的使用。\n适合显存小于 4GiB 的 GPU开启。\n如果您不确定,请保持此项为关闭。", + "SettingsTabGraphicsPreferredGpu": "首选 GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "选择Vulkan API使用的显卡。\n此选项不会影响OpenGL API。\n如果您不确定,建议选择\"dGPU(独立显卡)\"。如果没有独立显卡,则无需改动此选项", + "SettingsAppRequiredRestartMessage": "Ryujinx 需要重启", + "SettingsGpuBackendRestartMessage": "您修改了图形 API 或显卡设置。需要重新启动才能生效", + "SettingsGpuBackendRestartSubMessage": "是否重启模拟器?", + "RyujinxUpdaterMessage": "是否更新 Ryujinx 到最新的版本?", + "SettingsTabHotkeysVolumeUpHotkey": "音量加", + "SettingsTabHotkeysVolumeDownHotkey": "音量减", + "SettingsEnableMacroHLE": "启用 HLE 宏", + "SettingsEnableMacroHLETooltip": "GPU 宏代码的高级模拟。\n提高性能表现,但可能在某些游戏中引起图形错误。\n如果您不确定,请保持此项为开启。", + "VolumeShort": "音量", + "UserProfilesManageSaves": "管理存档", + "DeleteUserSave": "确定删除这个游戏的存档吗?", + "IrreversibleActionNote": "删除后不可恢复。", + "SaveManagerHeading": "管理 {0} 的存档", + "SaveManagerTitle": "存档管理器", + "Name": "名称", + "Size": "大小", + "Search": "搜索", + "UserProfilesRecoverLostAccounts": "恢复丢失的账户", + "Recover": "恢复", + "UserProfilesRecoverHeading": "找到了这些用户的存档数据" +} diff --git a/src/Ryujinx.Ava/Assets/Locales/zh_TW.json b/src/Ryujinx.Ava/Assets/Locales/zh_TW.json new file mode 100644 index 00000000..940282a0 --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Locales/zh_TW.json @@ -0,0 +1,614 @@ +{ + "Language": "繁體中文", + "MenuBarFileOpenApplet": "打開小程式", + "MenuBarFileOpenAppletOpenMiiAppletToolTip": "打開獨立的 Mii 小程式", + "SettingsTabInputDirectMouseAccess": "直通滑鼠操作", + "SettingsTabSystemMemoryManagerMode": "記憶體管理模式:", + "SettingsTabSystemMemoryManagerModeSoftware": "軟體", + "SettingsTabSystemMemoryManagerModeHost": "Host (快速)", + "SettingsTabSystemMemoryManagerModeHostUnchecked": "Host 略過檢查 (最快,但較不安全)", + "MenuBarFile": "_檔案", + "MenuBarFileOpenFromFile": "_載入檔案", + "MenuBarFileOpenUnpacked": "載入_解包後的遊戲", + "MenuBarFileOpenEmuFolder": "開啟 Ryujinx 資料夾", + "MenuBarFileOpenLogsFolder": "開啟日誌資料夾", + "MenuBarFileExit": "_退出", + "MenuBarOptions": "選項", + "MenuBarOptionsToggleFullscreen": "切換全螢幕模式", + "MenuBarOptionsStartGamesInFullscreen": "使用全螢幕模式啟動遊戲", + "MenuBarOptionsStopEmulation": "停止模擬", + "MenuBarOptionsSettings": "_設定", + "MenuBarOptionsManageUserProfiles": "_管理使用者帳號", + "MenuBarActions": "_動作", + "MenuBarOptionsSimulateWakeUpMessage": "模擬喚醒訊息", + "MenuBarActionsScanAmiibo": "掃描 Amiibo", + "MenuBarTools": "_工具", + "MenuBarToolsInstallFirmware": "安裝韌體", + "MenuBarFileToolsInstallFirmwareFromFile": "從 XCI 或 ZIP 安裝韌體", + "MenuBarFileToolsInstallFirmwareFromDirectory": "從資料夾安裝韌體", + "MenuBarHelp": "幫助", + "MenuBarHelpCheckForUpdates": "檢查更新", + "MenuBarHelpAbout": "關於", + "MenuSearch": "搜尋...", + "GameListHeaderFavorite": "收藏", + "GameListHeaderIcon": "圖示", + "GameListHeaderApplication": "名稱", + "GameListHeaderDeveloper": "開發商", + "GameListHeaderVersion": "版本", + "GameListHeaderTimePlayed": "遊玩時間", + "GameListHeaderLastPlayed": "上次遊玩", + "GameListHeaderFileExtension": "副檔名", + "GameListHeaderFileSize": "大小", + "GameListHeaderPath": "路徑", + "GameListContextMenuOpenUserSaveDirectory": "開啟使用者存檔資料夾", + "GameListContextMenuOpenUserSaveDirectoryToolTip": "開啟儲存遊戲存檔的資料夾", + "GameListContextMenuOpenDeviceSaveDirectory": "開啟系統資料夾", + "GameListContextMenuOpenDeviceSaveDirectoryToolTip": "開啟包含遊戲系統設定的資料夾", + "GameListContextMenuOpenBcatSaveDirectory": "開啟 BCAT 資料夾", + "GameListContextMenuOpenBcatSaveDirectoryToolTip": "開啟包含遊戲 BCAT 資料的資料夾", + "GameListContextMenuManageTitleUpdates": "管理遊戲更新", + "GameListContextMenuManageTitleUpdatesToolTip": "開啟更新管理視窗", + "GameListContextMenuManageDlc": "管理 DLC", + "GameListContextMenuManageDlcToolTip": "開啟 DLC 管理視窗", + "GameListContextMenuOpenModsDirectory": "開啟模組資料夾", + "GameListContextMenuOpenModsDirectoryToolTip": "開啟存放遊戲模組的資料夾", + "GameListContextMenuCacheManagement": "快取管理", + "GameListContextMenuCacheManagementPurgePptc": "清除 PPTC 快取", + "GameListContextMenuCacheManagementPurgePptcToolTip": "刪除遊戲的 PPTC 快取", + "GameListContextMenuCacheManagementPurgeShaderCache": "清除渲染器快取", + "GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "刪除遊戲的渲染器快取", + "GameListContextMenuCacheManagementOpenPptcDirectory": "開啟 PPTC 資料夾", + "GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "開啟包含遊戲 PPTC 快取的資料夾", + "GameListContextMenuCacheManagementOpenShaderCacheDirectory": "開啟渲染器快取資料夾", + "GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "開啟包含應用程式渲染器快取的資料夾", + "GameListContextMenuExtractData": "提取資料", + "GameListContextMenuExtractDataExeFS": "ExeFS", + "GameListContextMenuExtractDataExeFSToolTip": "從遊戲的目前狀態中提取 ExeFS 分區(包含更新)", + "GameListContextMenuExtractDataRomFS": "RomFS", + "GameListContextMenuExtractDataRomFSToolTip": "從遊戲的目前狀態中提取 RomFS 分區(包含更新)", + "GameListContextMenuExtractDataLogo": "圖示", + "GameListContextMenuExtractDataLogoToolTip": "從遊戲的目前狀態中提取圖示(包含更新)", + "StatusBarGamesLoaded": "{0}/{1} 遊戲載入完成", + "StatusBarSystemVersion": "系統版本: {0}", + "Settings": "設定", + "SettingsTabGeneral": "使用者介面", + "SettingsTabGeneralGeneral": "一般", + "SettingsTabGeneralEnableDiscordRichPresence": "啟用 Discord 動態狀態展示", + "SettingsTabGeneralCheckUpdatesOnLaunch": "自動檢查更新", + "SettingsTabGeneralShowConfirmExitDialog": "顯示「確認離開」對話框", + "SettingsTabGeneralHideCursorOnIdle": "自動隱藏滑鼠", + "SettingsTabGeneralGameDirectories": "遊戲資料夾", + "SettingsTabGeneralAdd": "新增", + "SettingsTabGeneralRemove": "刪除", + "SettingsTabSystem": "系統", + "SettingsTabSystemCore": "核心", + "SettingsTabSystemSystemRegion": "系統區域:", + "SettingsTabSystemSystemRegionJapan": "日本", + "SettingsTabSystemSystemRegionUSA": "美國", + "SettingsTabSystemSystemRegionEurope": "歐洲", + "SettingsTabSystemSystemRegionAustralia": "澳洲", + "SettingsTabSystemSystemRegionChina": "中國", + "SettingsTabSystemSystemRegionKorea": "韓國", + "SettingsTabSystemSystemRegionTaiwan": "台灣", + "SettingsTabSystemSystemLanguage": "系統語言:", + "SettingsTabSystemSystemLanguageJapanese": "日語", + "SettingsTabSystemSystemLanguageAmericanEnglish": "美式英語", + "SettingsTabSystemSystemLanguageFrench": "法語", + "SettingsTabSystemSystemLanguageGerman": "德語", + "SettingsTabSystemSystemLanguageItalian": "義大利語", + "SettingsTabSystemSystemLanguageSpanish": "西班牙語", + "SettingsTabSystemSystemLanguageChinese": "中文 (中國)", + "SettingsTabSystemSystemLanguageKorean": "韓語", + "SettingsTabSystemSystemLanguageDutch": "荷蘭語", + "SettingsTabSystemSystemLanguagePortuguese": "葡萄牙語", + "SettingsTabSystemSystemLanguageRussian": "俄語", + "SettingsTabSystemSystemLanguageTaiwanese": "中文 (台灣)", + "SettingsTabSystemSystemLanguageBritishEnglish": "英式英語", + "SettingsTabSystemSystemLanguageCanadianFrench": "加拿大法語", + "SettingsTabSystemSystemLanguageLatinAmericanSpanish": "拉美西班牙語", + "SettingsTabSystemSystemLanguageSimplifiedChinese": "簡體中文 (推薦)", + "SettingsTabSystemSystemLanguageTraditionalChinese": "繁體中文 (推薦)", + "SettingsTabSystemSystemTimeZone": "系統時區:", + "SettingsTabSystemSystemTime": "系統時鐘:", + "SettingsTabSystemEnableVsync": "開啟 VSync", + "SettingsTabSystemEnablePptc": "開啟 PPTC 快取", + "SettingsTabSystemEnableFsIntegrityChecks": "開啟檔案系統完整性檢查", + "SettingsTabSystemAudioBackend": "音訊後端:", + "SettingsTabSystemAudioBackendDummy": "無", + "SettingsTabSystemAudioBackendOpenAL": "OpenAL", + "SettingsTabSystemAudioBackendSoundIO": "SoundIO", + "SettingsTabSystemAudioBackendSDL2": "SDL2", + "SettingsTabSystemHacks": "修正", + "SettingsTabSystemHacksNote": " (會引起模擬器不穩定)", + "SettingsTabSystemExpandDramSize": "使用替代記憶體布局 (開發人員)", + "SettingsTabSystemIgnoreMissingServices": "忽略缺少的服務", + "SettingsTabGraphics": "圖形", + "SettingsTabGraphicsAPI": "圖形 API", + "SettingsTabGraphicsEnableShaderCache": "啟用渲染器快取", + "SettingsTabGraphicsAnisotropicFiltering": "各向異性過濾:", + "SettingsTabGraphicsAnisotropicFilteringAuto": "自動", + "SettingsTabGraphicsAnisotropicFiltering2x": "2 倍", + "SettingsTabGraphicsAnisotropicFiltering4x": "4 倍", + "SettingsTabGraphicsAnisotropicFiltering8x": "8 倍", + "SettingsTabGraphicsAnisotropicFiltering16x": "16倍", + "SettingsTabGraphicsResolutionScale": "解析度縮放:", + "SettingsTabGraphicsResolutionScaleCustom": "自訂 (不推薦)", + "SettingsTabGraphicsResolutionScaleNative": "原生 (720p/1080p)", + "SettingsTabGraphicsResolutionScale2x": "2 倍 (1440p/2160p)", + "SettingsTabGraphicsResolutionScale3x": "3 倍 (2160p/3240p)", + "SettingsTabGraphicsResolutionScale4x": "4 倍 (2880p/4320p)", + "SettingsTabGraphicsAspectRatio": "寬高比:", + "SettingsTabGraphicsAspectRatio4x3": "4:3", + "SettingsTabGraphicsAspectRatio16x9": "16:9", + "SettingsTabGraphicsAspectRatio16x10": "16:10", + "SettingsTabGraphicsAspectRatio21x9": "21:9", + "SettingsTabGraphicsAspectRatio32x9": "32:9", + "SettingsTabGraphicsAspectRatioStretch": "拉伸至螢幕大小", + "SettingsTabGraphicsDeveloperOptions": "開發者選項", + "SettingsTabGraphicsShaderDumpPath": "圖形渲染器轉儲路徑:", + "SettingsTabLogging": "日誌", + "SettingsTabLoggingLogging": "日誌", + "SettingsTabLoggingEnableLoggingToFile": "儲存日誌為檔案", + "SettingsTabLoggingEnableStubLogs": "記錄 Stub", + "SettingsTabLoggingEnableInfoLogs": "記錄資訊", + "SettingsTabLoggingEnableWarningLogs": "記錄警告", + "SettingsTabLoggingEnableErrorLogs": "記錄錯誤", + "SettingsTabLoggingEnableTraceLogs": "記錄 Trace", + "SettingsTabLoggingEnableGuestLogs": "記錄 Guest", + "SettingsTabLoggingEnableFsAccessLogs": "記錄檔案存取", + "SettingsTabLoggingFsGlobalAccessLogMode": "記錄全域檔案存取模式:", + "SettingsTabLoggingDeveloperOptions": "開發者選項 (警告: 會降低效能)", + "SettingsTabLoggingGraphicsBackendLogLevel": "圖形顯示後端日誌等級:", + "SettingsTabLoggingGraphicsBackendLogLevelNone": "無", + "SettingsTabLoggingGraphicsBackendLogLevelError": "錯誤", + "SettingsTabLoggingGraphicsBackendLogLevelPerformance": "減速", + "SettingsTabLoggingGraphicsBackendLogLevelAll": "全部", + "SettingsTabLoggingEnableDebugLogs": "啟用除錯日誌", + "SettingsTabInput": "輸入", + "SettingsTabInputEnableDockedMode": "Docked 模式", + "SettingsTabInputDirectKeyboardAccess": "直通鍵盤控制", + "SettingsButtonSave": "儲存", + "SettingsButtonClose": "關閉", + "SettingsButtonOk": "嘛好", + "SettingsButtonCancel": "取消", + "SettingsButtonApply": "套用", + "ControllerSettingsPlayer": "玩家", + "ControllerSettingsPlayer1": "玩家 1", + "ControllerSettingsPlayer2": "玩家 2", + "ControllerSettingsPlayer3": "玩家 3", + "ControllerSettingsPlayer4": "玩家 4", + "ControllerSettingsPlayer5": "玩家 5", + "ControllerSettingsPlayer6": "玩家 6", + "ControllerSettingsPlayer7": "玩家 7", + "ControllerSettingsPlayer8": "玩家 8", + "ControllerSettingsHandheld": "掌機模式", + "ControllerSettingsInputDevice": "輸入設備", + "ControllerSettingsRefresh": "更新", + "ControllerSettingsDeviceDisabled": "關閉", + "ControllerSettingsControllerType": "手把類型", + "ControllerSettingsControllerTypeHandheld": "掌機", + "ControllerSettingsControllerTypeProController": "Pro 手把", + "ControllerSettingsControllerTypeJoyConPair": "JoyCon", + "ControllerSettingsControllerTypeJoyConLeft": "左 JoyCon", + "ControllerSettingsControllerTypeJoyConRight": "右 JoyCon", + "ControllerSettingsProfile": "預設", + "ControllerSettingsProfileDefault": "預設", + "ControllerSettingsLoad": "載入", + "ControllerSettingsAdd": "建立", + "ControllerSettingsRemove": "刪除", + "ControllerSettingsButtons": "按鈕", + "ControllerSettingsButtonA": "A", + "ControllerSettingsButtonB": "B", + "ControllerSettingsButtonX": "X", + "ControllerSettingsButtonY": "Y", + "ControllerSettingsButtonPlus": "+", + "ControllerSettingsButtonMinus": "-", + "ControllerSettingsDPad": "方向鍵", + "ControllerSettingsDPadUp": "上", + "ControllerSettingsDPadDown": "下", + "ControllerSettingsDPadLeft": "左", + "ControllerSettingsDPadRight": "右", + "ControllerSettingsLStick": "左搖桿", + "ControllerSettingsLStickButton": "按下", + "ControllerSettingsLStickUp": "上", + "ControllerSettingsLStickDown": "下", + "ControllerSettingsLStickLeft": "左", + "ControllerSettingsLStickRight": "右", + "ControllerSettingsLStickStick": "桿", + "ControllerSettingsLStickInvertXAxis": "反轉 X 方向", + "ControllerSettingsLStickInvertYAxis": "反轉 Y 方向", + "ControllerSettingsLStickDeadzone": "死區:", + "ControllerSettingsRStick": "右搖桿", + "ControllerSettingsRStickButton": "按下", + "ControllerSettingsRStickUp": "上", + "ControllerSettingsRStickDown": "下", + "ControllerSettingsRStickLeft": "左", + "ControllerSettingsRStickRight": "右", + "ControllerSettingsRStickStick": "桿", + "ControllerSettingsRStickInvertXAxis": "反轉 X 方向", + "ControllerSettingsRStickInvertYAxis": "反轉 Y 方向", + "ControllerSettingsRStickDeadzone": "死區:", + "ControllerSettingsTriggersLeft": "左 Triggers", + "ControllerSettingsTriggersRight": "右 Triggers", + "ControllerSettingsTriggersButtonsLeft": "左 Triggers 鍵", + "ControllerSettingsTriggersButtonsRight": "右 Triggers 鍵", + "ControllerSettingsTriggers": "板機", + "ControllerSettingsTriggerL": "L", + "ControllerSettingsTriggerR": "R", + "ControllerSettingsTriggerZL": "ZL", + "ControllerSettingsTriggerZR": "ZR", + "ControllerSettingsLeftSL": "SL", + "ControllerSettingsLeftSR": "SR", + "ControllerSettingsRightSL": "SL", + "ControllerSettingsRightSR": "SR", + "ControllerSettingsExtraButtonsLeft": "左按鍵", + "ControllerSettingsExtraButtonsRight": "右按鍵", + "ControllerSettingsMisc": "其他", + "ControllerSettingsTriggerThreshold": "Triggers 閾值:", + "ControllerSettingsMotion": "體感", + "ControllerSettingsMotionUseCemuhookCompatibleMotion": "使用 CemuHook 體感協議", + "ControllerSettingsMotionControllerSlot": "手把:", + "ControllerSettingsMotionMirrorInput": "鏡像操作", + "ControllerSettingsMotionRightJoyConSlot": "右 JoyCon:", + "ControllerSettingsMotionServerHost": "伺服器 Host:", + "ControllerSettingsMotionGyroSensitivity": "陀螺儀敏感度:", + "ControllerSettingsMotionGyroDeadzone": "陀螺儀死區:", + "ControllerSettingsSave": "儲存", + "ControllerSettingsClose": "關閉", + "UserProfilesSelectedUserProfile": "選擇使用者帳號:", + "UserProfilesSaveProfileName": "儲存帳號名稱", + "UserProfilesChangeProfileImage": "更換頭貼", + "UserProfilesAvailableUserProfiles": "現有的帳號:", + "UserProfilesAddNewProfile": "建立帳號", + "UserProfilesDelete": "刪除", + "UserProfilesClose": "關閉", + "ProfileImageSelectionTitle": "頭貼選擇", + "ProfileImageSelectionHeader": "選擇合適的頭貼圖片", + "ProfileImageSelectionNote": "您可以導入自訂頭貼,或從系統中選擇頭貼", + "ProfileImageSelectionImportImage": "導入圖片檔案", + "ProfileImageSelectionSelectAvatar": "選擇系統頭貼", + "InputDialogTitle": "輸入對話框", + "InputDialogOk": "完成", + "InputDialogCancel": "取消", + "InputDialogAddNewProfileTitle": "選擇使用者名稱", + "InputDialogAddNewProfileHeader": "請輸入帳號名稱", + "InputDialogAddNewProfileSubtext": "(最大長度: {0})", + "AvatarChoose": "選擇", + "AvatarSetBackgroundColor": "設定背景顏色", + "AvatarClose": "關閉", + "ControllerSettingsLoadProfileToolTip": "載入預設", + "ControllerSettingsAddProfileToolTip": "新增預設", + "ControllerSettingsRemoveProfileToolTip": "刪除預設", + "ControllerSettingsSaveProfileToolTip": "儲存預設", + "MenuBarFileToolsTakeScreenshot": "儲存截圖", + "MenuBarFileToolsHideUi": "Hide UI", + "GameListContextMenuToggleFavorite": "標記為收藏", + "GameListContextMenuToggleFavoriteToolTip": "啟用或取消收藏標記", + "SettingsTabGeneralTheme": "主題", + "SettingsTabGeneralThemeCustomTheme": "自定主題路徑", + "SettingsTabGeneralThemeBaseStyle": "主題樣式", + "SettingsTabGeneralThemeBaseStyleDark": "深色模式", + "SettingsTabGeneralThemeBaseStyleLight": "淺色模式", + "SettingsTabGeneralThemeEnableCustomTheme": "使用自訂主題介面", + "ButtonBrowse": "瀏覽", + "ControllerSettingsConfigureGeneral": "配置", + "ControllerSettingsRumble": "震動", + "ControllerSettingsRumbleStrongMultiplier": "強震動調節", + "ControllerSettingsRumbleWeakMultiplier": "弱震動調節", + "DialogMessageSaveNotAvailableMessage": "沒有{0} [{1:x16}]的遊戲存檔", + "DialogMessageSaveNotAvailableCreateSaveMessage": "是否建立該遊戲的存檔資料夾?", + "DialogConfirmationTitle": "Ryujinx - 設定", + "DialogUpdaterTitle": "Ryujinx - 更新", + "DialogErrorTitle": "Ryujinx - 錯誤", + "DialogWarningTitle": "Ryujinx - 警告", + "DialogExitTitle": "Ryujinx - 關閉", + "DialogErrorMessage": "Ryujinx 遇到了錯誤", + "DialogExitMessage": "是否關閉 Ryujinx?", + "DialogExitSubMessage": "所有未儲存的進度會遺失!", + "DialogMessageCreateSaveErrorMessage": "建立特定的存檔時出錯: {0}", + "DialogMessageFindSaveErrorMessage": "查找特定的存檔時出錯: {0}", + "FolderDialogExtractTitle": "選擇要解壓到的資料夾", + "DialogNcaExtractionMessage": "提取{1}的{0}分區...", + "DialogNcaExtractionTitle": "Ryujinx - NCA分區提取", + "DialogNcaExtractionMainNcaNotFoundErrorMessage": "提取失敗。所選檔案中不含主NCA檔案", + "DialogNcaExtractionCheckLogErrorMessage": "提取失敗。請查看日誌檔案取得詳情。", + "DialogNcaExtractionSuccessMessage": "提取成功。", + "DialogUpdaterConvertFailedMessage": "無法轉換目前 Ryujinx 版本。", + "DialogUpdaterCancelUpdateMessage": "更新取消!", + "DialogUpdaterAlreadyOnLatestVersionMessage": "您使用的 Ryujinx 是最新版本。", + "DialogUpdaterFailedToGetVersionMessage": "嘗試從 Github 取得版本訊息時無效。可能是因為 GitHub Actions 正在編譯新版本。請過幾分鐘重試。", + "DialogUpdaterConvertFailedGithubMessage": "無法轉換從 Github 接收到的 Ryujinx 版本。", + "DialogUpdaterDownloadingMessage": "下載新版本中...", + "DialogUpdaterExtractionMessage": "正在提取更新...", + "DialogUpdaterRenamingMessage": "正在刪除舊檔案...", + "DialogUpdaterAddingFilesMessage": "安裝更新中...", + "DialogUpdaterCompleteMessage": "更新成功!", + "DialogUpdaterRestartMessage": "立即重啟 Ryujinx 完成更新?", + "DialogUpdaterArchNotSupportedMessage": "您執行的系統架構不受支援!", + "DialogUpdaterArchNotSupportedSubMessage": "(僅支援 x64 系統)", + "DialogUpdaterNoInternetMessage": "沒有連接到網路", + "DialogUpdaterNoInternetSubMessage": "請確保網路連接正常。", + "DialogUpdaterDirtyBuildMessage": "不能更新非官方版本的 Ryujinx!", + "DialogUpdaterDirtyBuildSubMessage": "如果希望使用受支援的版本,請您在 https://ryujinx.org/ 下載。", + "DialogRestartRequiredMessage": "需要重啟模擬器", + "DialogThemeRestartMessage": "主題設定已儲存。需要重新啟動才能生效。", + "DialogThemeRestartSubMessage": "您是否要重啟?", + "DialogFirmwareInstallEmbeddedMessage": "要安裝遊戲內建的韌體嗎?(韌體 {0})", + "DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安裝的韌體,但 Ryujinx 可以從現有的遊戲安裝韌體{0}.\\n模擬器現在可以執行。", + "DialogFirmwareNoFirmwareInstalledMessage": "未安裝韌體", + "DialogFirmwareInstalledMessage": "已安裝韌體{0}", + "DialogOpenSettingsWindowLabel": "打開設定視窗", + "DialogControllerAppletTitle": "控制器小視窗", + "DialogMessageDialogErrorExceptionMessage": "顯示訊息對話框時出錯: {0}", + "DialogSoftwareKeyboardErrorExceptionMessage": "顯示軟體鍵盤時出錯: {0}", + "DialogErrorAppletErrorExceptionMessage": "顯示錯誤對話框時出錯: {0}", + "DialogUserErrorDialogMessage": "{0}: {1}", + "DialogUserErrorDialogInfoMessage": "\n有關修復此錯誤的更多訊息,可以遵循我們的設定指南。", + "DialogUserErrorDialogTitle": "Ryujinx 錯誤 ({0})", + "DialogAmiiboApiTitle": "Amiibo API", + "DialogAmiiboApiFailFetchMessage": "從 API 取得訊息時出錯。", + "DialogAmiiboApiConnectErrorMessage": "無法連接到 Amiibo API 伺服器。伺服器可能已關閉,或者您沒有網路連接。", + "DialogProfileInvalidProfileErrorMessage": "預設{0} 與目前輸入配置系統不相容。", + "DialogProfileDefaultProfileOverwriteErrorMessage": "無法覆蓋預設的配置檔案", + "DialogProfileDeleteProfileTitle": "刪除配置檔", + "DialogProfileDeleteProfileMessage": "刪除後不可恢復,確定嗎?", + "DialogWarning": "警告", + "DialogPPTCDeletionMessage": "您即將刪除:\n\n{0}的 PPTC 快取\n\n確定嗎?", + "DialogPPTCDeletionErrorMessage": "清除位於{0}的 PPTC 快取時出錯: {1}", + "DialogShaderDeletionMessage": "您即將刪除:\n\n{0}的渲染器快取\n\n確定嗎?", + "DialogShaderDeletionErrorMessage": "清除位於{0}的渲染器快取時出錯: {1}", + "DialogRyujinxErrorMessage": "Ryujinx 遇到錯誤", + "DialogInvalidTitleIdErrorMessage": "UI 錯誤:所選遊戲沒有有效的標題ID", + "DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "路徑{0}找不到有效的系統韌體。", + "DialogFirmwareInstallerFirmwareInstallTitle": "安裝韌體{0}", + "DialogFirmwareInstallerFirmwareInstallMessage": "將安裝{0}版本的系統。", + "DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n這將替換目前系統版本{0}。", + "DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n確認進行?", + "DialogFirmwareInstallerFirmwareInstallWaitMessage": "安裝韌體中...", + "DialogFirmwareInstallerFirmwareInstallSuccessMessage": "成功安裝系統版本{0}。", + "DialogUserProfileDeletionWarningMessage": "刪除後將沒有可選擇的使用者帳號", + "DialogUserProfileDeletionConfirmMessage": "是否刪除選擇的帳號", + "DialogControllerSettingsModifiedConfirmMessage": "目前的輸入預設已更新", + "DialogControllerSettingsModifiedConfirmSubMessage": "要儲存嗎?", + "DialogLoadNcaErrorMessage": "{0}. 錯誤的檔案: {1}", + "DialogDlcNoDlcErrorMessage": "選擇的檔案不包含所選遊戲的 DLC!", + "DialogPerformanceCheckLoggingEnabledMessage": "您啟用了跟蹤日誌,僅供開發人員使用。", + "DialogPerformanceCheckLoggingEnabledConfirmMessage": "為了獲得最佳效能,建議停用跟蹤日誌記錄。您是否要立即停用?", + "DialogPerformanceCheckShaderDumpEnabledMessage": "您啟用了渲染器轉儲,僅供開發人員使用。", + "DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "為了獲得最佳效能,建議停用渲染器轉儲。您是否要立即停用?", + "DialogLoadAppGameAlreadyLoadedMessage": "目前已載入有遊戲", + "DialogLoadAppGameAlreadyLoadedSubMessage": "請停止模擬或關閉程式,再啟動另一個遊戲。", + "DialogUpdateAddUpdateErrorMessage": "選擇的檔案不包含所選遊戲的更新!", + "DialogSettingsBackendThreadingWarningTitle": "警告 - 後端多執行緒", + "DialogSettingsBackendThreadingWarningMessage": "改變此選項後必須重啟 Ryujinx 才能生效。根據您的硬體,您開啟該選項時,可能需要手動停用驅動程式本身的GL多執行緒。", + "SettingsTabGraphicsFeaturesOptions": "功能", + "SettingsTabGraphicsBackendMultithreading": "後端多執行緒:", + "CommonAuto": "自動(推薦)", + "CommonOff": "關閉", + "CommonOn": "打開", + "InputDialogYes": "是", + "InputDialogNo": "否", + "DialogProfileInvalidProfileNameErrorMessage": "檔案名包含無效字元,請重試。", + "MenuBarOptionsPauseEmulation": "暫停", + "MenuBarOptionsResumeEmulation": "繼續", + "AboutUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的官網。", + "AboutDisclaimerMessage": "Ryujinx 以任何方式與 Nintendo™ 及其合作伙伴都沒有任何關聯。", + "AboutAmiiboDisclaimerMessage": "我們的 Amiibo 模擬使用了\nAmiiboAPI (www.amiiboapi.com) ", + "AboutPatreonUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 Patreon 贊助頁。", + "AboutGithubUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 GitHub 儲存庫。", + "AboutDiscordUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 Discord 伺服器邀請連結。", + "AboutTwitterUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 Twitter 首頁。", + "AboutRyujinxAboutTitle": "關於:", + "AboutRyujinxAboutContent": "Ryujinx 是一款 Nintendo Switch™ 模擬器。\n您可以在 Patreon 上贊助 Ryujinx。\n關注 Twitter 或 Discord 可以取得模擬器最新動態。\n如果您對開發本軟體感興趣,歡迎來 GitHub 和 Discord 加入我們!", + "AboutRyujinxMaintainersTitle": "由以下作者維護:", + "AboutRyujinxMaintainersContentTooltipMessage": "在瀏覽器中打開貢獻者的網頁", + "AboutRyujinxSupprtersTitle": "感謝 Patreon 的贊助者:", + "AmiiboSeriesLabel": "Amiibo 系列", + "AmiiboCharacterLabel": "角色", + "AmiiboScanButtonLabel": "掃描", + "AmiiboOptionsShowAllLabel": "顯示所有 Amiibo", + "AmiiboOptionsUsRandomTagLabel": "修正: 使用隨機標記的 Uuid", + "DlcManagerTableHeadingEnabledLabel": "啟用", + "DlcManagerTableHeadingTitleIdLabel": "遊戲ID", + "DlcManagerTableHeadingContainerPathLabel": "資料夾路徑", + "DlcManagerTableHeadingFullPathLabel": "完整路徑", + "DlcManagerRemoveAllButton": "全部刪除", + "DlcManagerEnableAllButton": "啟用全部", + "DlcManagerDisableAllButton": "停用全部", + "MenuBarOptionsChangeLanguage": "變更語言", + "CommonSort": "排序", + "CommonShowNames": "顯示名稱", + "CommonFavorite": "收藏", + "OrderAscending": "從小到大", + "OrderDescending": "從大到小", + "SettingsTabGraphicsFeatures": "額外功能", + "ErrorWindowTitle": "錯誤視窗", + "ToggleDiscordTooltip": "啟用或關閉 Discord 動態狀態展示", + "AddGameDirBoxTooltip": "輸入要添加的遊戲資料夾", + "AddGameDirTooltip": "添加遊戲資料夾到列表中", + "RemoveGameDirTooltip": "移除選中的資料夾", + "CustomThemeCheckTooltip": "啟用或關閉自訂主題", + "CustomThemePathTooltip": "自訂主題的資料夾", + "CustomThemeBrowseTooltip": "查找自訂主題", + "DockModeToggleTooltip": "是否開啟 Switch 的 Docked 模式", + "DirectKeyboardTooltip": "是否開啟\"直連鍵盤存取(HID) 支援\"\n(部分遊戲可以使用您的鍵盤輸入文字)", + "DirectMouseTooltip": "是否開啟\"直連滑鼠存取(HID) 支援\"\n(部分遊戲可以使用您的滑鼠導航)", + "RegionTooltip": "變更系統區域", + "LanguageTooltip": "變更系統語言", + "TimezoneTooltip": "變更系統時區", + "TimeTooltip": "變更系統時鐘", + "VSyncToggleTooltip": "關閉後,部分使用動態更新率的遊戲可以超過 60Hz 更新率", + "PptcToggleTooltip": "開啟以後減少遊戲啟動時間和卡頓", + "FsIntegrityToggleTooltip": "是否檢查遊戲檔案內容的完整性", + "AudioBackendTooltip": "預設推薦 SDL,但每種音訊後端對各類遊戲相容性不同,遇到音訊問題可以切換後端", + "MemoryManagerTooltip": "改變 Switch 記憶體映射到電腦記憶體的方式,會影響CPU效能消耗", + "MemoryManagerSoftwareTooltip": "使用軟體記憶體頁管理,最精確但是速度最慢", + "MemoryManagerHostTooltip": "直接映射記憶體頁到電腦記憶體, JIT 效率高", + "MemoryManagerUnsafeTooltip": "直接映射記憶體頁,但是不檢查記憶體溢出,JIT效率最高。\nRyujinx 可以存取任何位置的記憶體,因而相對不安全。此模式下只應執行您信任的遊戲或軟體(即官方遊戲)", + "DRamTooltip": "擴充模擬的 Switch 記憶體為 6GiB,某些高畫質材質模組或 4K 模組需要此選項\n這並不會提升性能\n\n如果不確定請關閉本功能", + "IgnoreMissingServicesTooltip": "忽略某些未實作的系統服務,少部分遊戲需要此選項才能啟動", + "GraphicsBackendThreadingTooltip": "啟用後端多執行緒", + "GalThreadingTooltip": "使用模擬器自帶的多執行緒調度,減少渲染器編譯的卡頓,並提高驅動程式的效能(尤其是缺失多執行緒的AMD)。\nNVIDIA使用者需要重啟模擬器才能停用驅動本身的多執行緒,否則您需手動執行停用獲得最佳效能", + "ShaderCacheToggleTooltip": "開啟後快取渲染器到硬碟,減少遊戲卡頓", + "ResolutionScaleTooltip": "縮放渲染的解析度", + "ResolutionScaleEntryTooltip": "盡量使用如1.5的浮點倍數。非整數的倍率易引起錯誤", + "AnisotropyTooltip": "各向異性過濾等級。提高傾斜視角材質的清晰度\n(選擇「自動」將使用遊戲預設指定的等級)", + "AspectRatioTooltip": "模擬器渲染視窗的寬高比", + "ShaderDumpPathTooltip": "轉儲圖形渲染器的路徑", + "FileLogTooltip": "是否儲存日誌檔案到硬碟", + "StubLogTooltip": "記錄 Stub 訊息", + "InfoLogTooltip": "記錄資訊訊息", + "WarnLogTooltip": "記錄警告訊息", + "ErrorLogTooltip": "記錄錯誤訊息", + "TraceLogTooltip": "記錄 Trace 訊息", + "GuestLogTooltip": "記錄 Guest 訊息", + "FileAccessLogTooltip": "記錄檔案存取訊息", + "FSAccessLogModeTooltip": "記錄 FS 存取訊息,輸出到控制台。可選的模式是 0-3", + "DeveloperOptionTooltip": "使用請謹慎", + "OpenGlLogLevel": "需要打開適當的日誌等級", + "DebugLogTooltip": "記錄Debug訊息", + "LoadApplicationFileTooltip": "選擇 Switch 支援的遊戲格式並載入", + "LoadApplicationFolderTooltip": "選擇解包後的 Switch 遊戲並載入", + "OpenRyujinxFolderTooltip": "打開 Ryujinx 系統資料夾", + "OpenRyujinxLogsTooltip": "打開日誌存放的資料夾", + "ExitTooltip": "關閉 Ryujinx", + "OpenSettingsTooltip": "打開設定視窗", + "OpenProfileManagerTooltip": "打開使用者帳號管理器", + "StopEmulationTooltip": "停止執行目前遊戲並回到選擇界面", + "CheckUpdatesTooltip": "檢查 Ryujinx 新版本", + "OpenAboutTooltip": "開啟關於視窗", + "GridSize": "網格尺寸", + "GridSizeTooltip": "調整網格模式的大小", + "SettingsTabSystemSystemLanguageBrazilianPortuguese": "巴西葡萄牙語", + "AboutRyujinxContributorsButtonHeader": "查看所有參與者", + "SettingsTabSystemAudioVolume": "音量: ", + "AudioVolumeTooltip": "調節音量", + "SettingsTabSystemEnableInternetAccess": "啟用網路連接", + "EnableInternetAccessTooltip": "開啟網路存取。此選項打開後,效果類似於 Switch 連接到網路的狀態。注意即使此選項關閉,應用程式偶爾也有可能連接到網路", + "GameListContextMenuManageCheatToolTip": "管理金手指", + "GameListContextMenuManageCheat": "管理金手指", + "ControllerSettingsStickRange": "範圍", + "DialogStopEmulationTitle": "Ryujinx - 停止模擬", + "DialogStopEmulationMessage": "是否確定停止模擬?", + "SettingsTabCpu": "CPU", + "SettingsTabAudio": "音訊", + "SettingsTabNetwork": "網路", + "SettingsTabNetworkConnection": "網路連接", + "SettingsTabCpuCache": "CPU 快取", + "SettingsTabCpuMemory": "CPU 記憶體", + "DialogUpdaterFlatpakNotSupportedMessage": "請透過 Flathub 更新 Ryujinx。", + "UpdaterDisabledWarningTitle": "更新已停用!", + "GameListContextMenuOpenSdModsDirectory": "打開 Atmosphere 模組資料夾", + "GameListContextMenuOpenSdModsDirectoryToolTip": "打開包含應用程式模組的額外 Atmosphere SD卡資料夾", + "ControllerSettingsRotate90": "順時針旋轉 90°", + "IconSize": "圖示尺寸", + "IconSizeTooltip": "變更遊戲圖示大小", + "MenuBarOptionsShowConsole": "顯示控制台", + "ShaderCachePurgeError": "清除渲染器快取時出錯: {0}: {1}", + "UserErrorNoKeys": "找不到金鑰", + "UserErrorNoFirmware": "找不到韌體", + "UserErrorFirmwareParsingFailed": "韌體解析錯誤", + "UserErrorApplicationNotFound": "找不到應用程式", + "UserErrorUnknown": "未知錯誤", + "UserErrorUndefined": "未定義錯誤", + "UserErrorNoKeysDescription": "Ryujinx 找不到 『prod.keys』 檔案", + "UserErrorNoFirmwareDescription": "Ryujinx 找不到任何已安裝的韌體", + "UserErrorFirmwareParsingFailedDescription": "Ryujinx 無法解密選擇的韌體。這通常是由於金鑰過舊。", + "UserErrorApplicationNotFoundDescription": "Ryujinx 在選中路徑找不到有效的應用程式。", + "UserErrorUnknownDescription": "發生未知錯誤!", + "UserErrorUndefinedDescription": "發生了未定義錯誤!此類錯誤不應出現,請聯絡開發人員!", + "OpenSetupGuideMessage": "打開設定教學", + "NoUpdate": "沒有新版本", + "TitleUpdateVersionLabel": "版本 {0} - {1}", + "RyujinxInfo": "Ryujinx - 訊息", + "RyujinxConfirm": "Ryujinx - 確認", + "FileDialogAllTypes": "全部類型", + "Never": "從不", + "SwkbdMinCharacters": "至少應為 {0} 個字長", + "SwkbdMinRangeCharacters": "必須為 {0}-{1} 個字長", + "SoftwareKeyboard": "軟體鍵盤", + "DialogControllerAppletMessagePlayerRange": "本遊戲需要 {0} 個玩家持有:\n\n類型:{1}\n\n玩家:{2}\n\n{3}請打開設定畫面,配置手把,或者關閉本視窗。", + "DialogControllerAppletMessage": "本遊戲需要剛好 {0} 個玩家持有:\n\n類型:{1}\n\n玩家:{2}\n\n{3}請打開設定畫面,配置手把,或者關閉本視窗。", + "DialogControllerAppletDockModeSet": "現在處於主機模式,無法使用掌機操作方式\n\n", + "UpdaterRenaming": "正在重新命名舊檔案...", + "UpdaterRenameFailed": "更新過程中無法重新命名檔案: {0}", + "UpdaterAddingFiles": "安裝更新中...", + "UpdaterExtracting": "正在提取更新...", + "UpdaterDownloading": "下載新版本中...", + "Game": "遊戲", + "Docked": "主機模式", + "Handheld": "掌機模式", + "ConnectionError": "連接錯誤。", + "AboutPageDeveloperListMore": "{0} 等開發者...", + "ApiError": "API 錯誤", + "LoadingHeading": "正在啟動 {0}", + "CompilingPPTC": "編譯 PPTC 快取中", + "CompilingShaders": "編譯渲染器中", + "AllKeyboards": "所有鍵盤", + "OpenFileDialogTitle": "選擇支援的檔案格式", + "OpenFolderDialogTitle": "選擇一個包含已解包遊戲的資料夾", + "AllSupportedFormats": "全部支援的格式", + "RyujinxUpdater": "Ryujinx 更新程式", + "SettingsTabHotkeys": "快捷鍵", + "SettingsTabHotkeysHotkeys": "鍵盤快捷鍵", + "SettingsTabHotkeysToggleVsyncHotkey": "切換垂直同步", + "SettingsTabHotkeysScreenshotHotkey": "截圖", + "SettingsTabHotkeysShowUiHotkey": "隱藏使用者介面", + "SettingsTabHotkeysPauseHotkey": "暫停", + "SettingsTabHotkeysToggleMuteHotkey": "靜音", + "ControllerMotionTitle": "體感操作設定", + "ControllerRumbleTitle": "震動設定", + "SettingsSelectThemeFileDialogTitle": "選擇主題檔案", + "SettingsXamlThemeFile": "Xaml 主題檔案", + "AvatarWindowTitle": "管理帳號 - 頭貼", + "Amiibo": "Amiibo", + "Unknown": "未知", + "Usage": "用途", + "Writable": "可寫入", + "SelectDlcDialogTitle": "選擇 DLC 檔案", + "SelectUpdateDialogTitle": "選擇更新檔", + "UserProfileWindowTitle": "管理使用者設定檔", + "CheatWindowTitle": "管理遊戲金手指", + "DlcWindowTitle": "管理遊戲 DLC", + "UpdateWindowTitle": "管理遊戲更新", + "CheatWindowHeading": "金手指可用於 {0} [{1}]", + "DlcWindowHeading": "DLC 可用於 {0} [{1}]", + "UserProfilesEditProfile": "編輯所選", + "Cancel": "取消", + "Save": "儲存", + "Discard": "放棄變更", + "UserProfilesSetProfileImage": "設定帳號頭貼", + "UserProfileEmptyNameError": "使用者名稱為必填", + "UserProfileNoImageError": "必須設定帳號頭貼", + "GameUpdateWindowHeading": "更新可用於 {0} [{1}]", + "SettingsTabHotkeysResScaleUpHotkey": "提高解析度:", + "SettingsTabHotkeysResScaleDownHotkey": "降低解析度:", + "UserProfilesName": "使用者名稱:", + "UserProfilesUserId": "使用者 ID:", + "SettingsTabGraphicsBackend": "圖形顯示後端", + "SettingsTabGraphicsBackendTooltip": "要用來處理圖形相關內容的後端", + "SettingsEnableTextureRecompression": "開啟材質重新壓縮", + "SettingsEnableTextureRecompressionTooltip": "壓縮某些材質以減少 VRAM 使用。\n\n推薦用於小於 4GiB VRAM 的 GPU。\n\n如果不確定請關閉本功能。", + "SettingsTabGraphicsPreferredGpu": "首選 GPU", + "SettingsTabGraphicsPreferredGpuTooltip": "選擇與 Vulkan 圖形顯示後端一起使用的 GPU。\n\n不影響 OpenGL 圖形後端所使用的 GPU。\n\n如果不確定,請設定為標記為「dGPU」的 GPU。如果沒有,請保持原設定。", + "SettingsAppRequiredRestartMessage": "必須重啟 Ryujinx", + "SettingsGpuBackendRestartMessage": "圖形顯示後端或 GPU 相關設定已被修改。需要重新啟動才能套用。", + "SettingsGpuBackendRestartSubMessage": "您想要現在重新啟動本程式嗎?", + "RyujinxUpdaterMessage": "您想要將 Ryujinx 更新到最新版本嗎?", + "SettingsTabHotkeysVolumeUpHotkey": "增加音量:", + "SettingsTabHotkeysVolumeDownHotkey": "降低音量:", + "SettingsEnableMacroHLE": "啟用 Macro HLE", + "SettingsEnableMacroHLETooltip": "GPU Macro 代碼的進階模擬。\n\n可以提升性能,但可能會導致某些遊戲出現圖形顯示故障。\n\n如果不確定請關閉本功能。", + "VolumeShort": "音量", + "UserProfilesManageSaves": "管理遊戲存檔", + "DeleteUserSave": "您想要刪除本遊戲的存檔嗎?", + "IrreversibleActionNote": "本動作將無法挽回。", + "SaveManagerHeading": "管理 {0} 的遊戲存檔", + "SaveManagerTitle": "遊戲存檔管理器", + "Name": "名稱", + "Size": "大小", + "Search": "搜尋", + "UserProfilesRecoverLostAccounts": "恢復遺失的帳號", + "Recover": "恢復", + "UserProfilesRecoverHeading": "在以下帳號找到了一些遊戲存檔" +} diff --git a/src/Ryujinx.Ava/Assets/Styles/BaseDark.xaml b/src/Ryujinx.Ava/Assets/Styles/BaseDark.xaml new file mode 100644 index 00000000..c7f6266f --- /dev/null +++ b/src/Ryujinx.Ava/Assets/Styles/BaseDark.xaml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + +